diff options
Diffstat (limited to 'src/librustdoc')
64 files changed, 2552 insertions, 1381 deletions
diff --git a/src/librustdoc/Cargo.toml b/src/librustdoc/Cargo.toml index f3917b978..e13e95ef7 100644 --- a/src/librustdoc/Cargo.toml +++ b/src/librustdoc/Cargo.toml @@ -9,7 +9,7 @@ path = "lib.rs" [dependencies] arrayvec = { version = "0.7", default-features = false } askama = { version = "0.12", default-features = false, features = ["config"] } -itertools = "0.10.1" +itertools = "0.11" indexmap = "2" minifier = "0.3.0" once_cell = "1.10.0" diff --git a/src/librustdoc/clean/auto_trait.rs b/src/librustdoc/clean/auto_trait.rs index bdf6a0f6b..e692f4ef7 100644 --- a/src/librustdoc/clean/auto_trait.rs +++ b/src/librustdoc/clean/auto_trait.rs @@ -1,9 +1,7 @@ -use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::lang_items::LangItem; -use rustc_middle::ty::{self, Region, RegionVid, TypeFoldable}; +use rustc_middle::ty::{Region, RegionVid, TypeFoldable}; use rustc_trait_selection::traits::auto_trait::{self, AutoTraitResult}; -use thin_vec::ThinVec; use std::fmt::Debug; @@ -723,7 +721,7 @@ where fn region_name(region: Region<'_>) -> Option<Symbol> { match *region { - ty::ReEarlyBound(r) => Some(r.name), + ty::ReEarlyParam(r) => Some(r.name), _ => None, } } @@ -743,7 +741,7 @@ impl<'a, 'tcx> TypeFolder<TyCtxt<'tcx>> for RegionReplacer<'a, 'tcx> { match *r { // These are the regions that can be seen in the AST. ty::ReVar(vid) => self.vid_to_region.get(&vid).cloned().unwrap_or(r), - ty::ReEarlyBound(_) | ty::ReStatic | ty::ReLateBound(..) | ty::ReError(_) => r, + ty::ReEarlyParam(_) | ty::ReStatic | ty::ReBound(..) | ty::ReError(_) => r, r => bug!("unexpected region: {r:?}"), } } diff --git a/src/librustdoc/clean/blanket_impl.rs b/src/librustdoc/clean/blanket_impl.rs index dad2aa406..4da85885d 100644 --- a/src/librustdoc/clean/blanket_impl.rs +++ b/src/librustdoc/clean/blanket_impl.rs @@ -14,7 +14,6 @@ pub(crate) struct BlanketImplFinder<'a, 'tcx> { impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { pub(crate) fn get_blanket_impls(&mut self, item_def_id: DefId) -> Vec<Item> { let cx = &mut self.cx; - let param_env = cx.tcx.param_env(item_def_id); let ty = cx.tcx.type_of(item_def_id); trace!("get_blanket_impls({ty:?})"); @@ -40,7 +39,7 @@ impl<'a, 'tcx> BlanketImplFinder<'a, 'tcx> { let infcx = cx.tcx.infer_ctxt().build(); let args = infcx.fresh_args_for_item(DUMMY_SP, item_def_id); let impl_ty = ty.instantiate(infcx.tcx, args); - let param_env = EarlyBinder::bind(param_env).instantiate(infcx.tcx, args); + let param_env = ty::ParamEnv::empty(); let impl_args = infcx.fresh_args_for_item(DUMMY_SP, impl_def_id); let impl_trait_ref = trait_ref.instantiate(infcx.tcx, impl_args); diff --git a/src/librustdoc/clean/cfg.rs b/src/librustdoc/clean/cfg.rs index ab5aec12f..0445daf0d 100644 --- a/src/librustdoc/clean/cfg.rs +++ b/src/librustdoc/clean/cfg.rs @@ -164,8 +164,8 @@ impl Cfg { /// Renders the configuration for human display, as a short HTML description. pub(crate) fn render_short_html(&self) -> String { let mut msg = Display(self, Format::ShortHtml).to_string(); - if self.should_capitalize_first_letter() && - let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) + if self.should_capitalize_first_letter() + && let Some(i) = msg.find(|c: char| c.is_ascii_alphanumeric()) { msg[i..i + 1].make_ascii_uppercase(); } @@ -516,7 +516,6 @@ impl<'a> fmt::Display for Display<'a> { (sym::target_arch, Some(arch)) => match arch.as_str() { "aarch64" => "AArch64", "arm" => "ARM", - "asmjs" => "JavaScript", "loongarch64" => "LoongArch LA64", "m68k" => "M68k", "csky" => "CSKY", diff --git a/src/librustdoc/clean/cfg/tests.rs b/src/librustdoc/clean/cfg/tests.rs index bb62660e1..20bcf1abf 100644 --- a/src/librustdoc/clean/cfg/tests.rs +++ b/src/librustdoc/clean/cfg/tests.rs @@ -1,8 +1,8 @@ use super::*; -use rustc_ast::{LitKind, MetaItemLit, Path, StrStyle}; +use rustc_ast::{MetaItemLit, Path, StrStyle}; use rustc_span::create_default_session_globals_then; -use rustc_span::symbol::{kw, Ident, Symbol}; +use rustc_span::symbol::{kw, Ident}; use rustc_span::DUMMY_SP; use thin_vec::thin_vec; diff --git a/src/librustdoc/clean/inline.rs b/src/librustdoc/clean/inline.rs index 974ba1e3b..014bcb1a8 100644 --- a/src/librustdoc/clean/inline.rs +++ b/src/librustdoc/clean/inline.rs @@ -374,15 +374,17 @@ pub(crate) fn build_impl( // Only inline impl if the implemented trait is // reachable in rustdoc generated documentation - if !did.is_local() && let Some(traitref) = associated_trait { + if !did.is_local() + && let Some(traitref) = associated_trait + { let did = traitref.def_id; if !cx.cache.effective_visibilities.is_directly_public(tcx, did) { return; } - if let Some(stab) = tcx.lookup_stability(did) && - stab.is_unstable() && - stab.feature == sym::rustc_private + if let Some(stab) = tcx.lookup_stability(did) + && stab.is_unstable() + && stab.feature == sym::rustc_private { return; } @@ -514,7 +516,10 @@ pub(crate) fn build_impl( } while let Some(ty) = stack.pop() { - if let Some(did) = ty.def_id(&cx.cache) && !document_hidden && tcx.is_doc_hidden(did) { + if let Some(did) = ty.def_id(&cx.cache) + && !document_hidden + && tcx.is_doc_hidden(did) + { return; } if let Some(generics) = ty.generics() { @@ -580,7 +585,8 @@ fn build_module_items( let res = item.res.expect_non_local(); if let Some(def_id) = res.opt_def_id() && let Some(allowed_def_ids) = allowed_def_ids - && !allowed_def_ids.contains(&def_id) { + && !allowed_def_ids.contains(&def_id) + { continue; } if let Some(def_id) = res.mod_def_id() { @@ -634,7 +640,7 @@ fn build_module_items( pub(crate) fn print_inlined_const(tcx: TyCtxt<'_>, did: DefId) -> String { if let Some(did) = did.as_local() { - let hir_id = tcx.hir().local_def_id_to_hir_id(did); + let hir_id = tcx.local_def_id_to_hir_id(did); rustc_hir_pretty::id_to_string(&tcx.hir(), hir_id) } else { tcx.rendered_const(did).clone() diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 1b7ca7bf7..75f9560f5 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -26,7 +26,7 @@ use rustc_middle::middle::resolve_bound_vars as rbv; use rustc_middle::ty::fold::TypeFolder; use rustc_middle::ty::GenericArgsRef; use rustc_middle::ty::TypeVisitableExt; -use rustc_middle::ty::{self, AdtKind, EarlyBinder, Ty, TyCtxt}; +use rustc_middle::ty::{self, AdtKind, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::hygiene::{AstPass, MacroKind}; use rustc_span::symbol::{kw, sym, Ident, Symbol}; @@ -54,7 +54,9 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< let mut inserted = FxHashSet::default(); items.extend(doc.foreigns.iter().map(|(item, renamed)| { let item = clean_maybe_renamed_foreign_item(cx, item, *renamed); - if let Some(name) = item.name && (cx.render_options.document_hidden || !item.is_doc_hidden()) { + if let Some(name) = item.name + && (cx.render_options.document_hidden || !item.is_doc_hidden()) + { inserted.insert((item.type_(), name)); } item @@ -85,7 +87,9 @@ pub(crate) fn clean_doc_module<'tcx>(doc: &DocModule<'tcx>, cx: &mut DocContext< } let v = clean_maybe_renamed_item(cx, item, *renamed, *import_id); for item in &v { - if let Some(name) = item.name && (cx.render_options.document_hidden || !item.is_doc_hidden()) { + if let Some(name) = item.name + && (cx.render_options.document_hidden || !item.is_doc_hidden()) + { inserted.insert((item.type_(), name)); } } @@ -180,22 +184,6 @@ fn clean_generic_bound<'tcx>( ) -> Option<GenericBound> { Some(match *bound { hir::GenericBound::Outlives(lt) => GenericBound::Outlives(clean_lifetime(lt, cx)), - hir::GenericBound::LangItemTrait(lang_item, span, _, generic_args) => { - let def_id = cx.tcx.require_lang_item(lang_item, Some(span)); - - let trait_ref = ty::Binder::dummy(ty::TraitRef::identity(cx.tcx, def_id)); - - let generic_args = clean_generic_args(generic_args, cx); - let GenericArgs::AngleBracketed { bindings, .. } = generic_args else { - bug!("clean: parenthesized `GenericBound::LangItemTrait`"); - }; - - let trait_ = clean_trait_ref_with_bindings(cx, trait_ref, bindings); - GenericBound::TraitBound( - PolyTrait { trait_, generic_params: vec![] }, - hir::TraitBoundModifier::None, - ) - } hir::GenericBound::Trait(ref t, modifier) => { // `T: ~const Destruct` is hidden because `T: Destruct` is a no-op. if modifier == hir::TraitBoundModifier::MaybeConst @@ -286,12 +274,10 @@ pub(crate) fn clean_middle_region<'tcx>(region: ty::Region<'tcx>) -> Option<Life match *region { ty::ReStatic => Some(Lifetime::statik()), _ if !region.has_name() => None, - ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) => { - Some(Lifetime(name)) - } - ty::ReEarlyBound(ref data) => Some(Lifetime(data.name)), - ty::ReLateBound(..) - | ty::ReFree(..) + ty::ReBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) => Some(Lifetime(name)), + ty::ReEarlyParam(ref data) => Some(Lifetime(data.name)), + ty::ReBound(..) + | ty::ReLateParam(..) | ty::ReVar(..) | ty::ReError(_) | ty::RePlaceholder(..) @@ -593,13 +579,13 @@ fn clean_generic_param<'tcx>( }, ) } - hir::GenericParamKind::Const { ty, default } => ( + hir::GenericParamKind::Const { ty, default, is_host_effect } => ( param.name.ident().name, GenericParamDefKind::Const { ty: Box::new(clean_ty(ty, cx)), default: default .map(|ct| Box::new(ty::Const::from_anon_const(cx.tcx, ct.def_id).to_string())), - is_host_effect: cx.tcx.has_attr(param.def_id, sym::rustc_host), + is_host_effect, }, ), }; @@ -741,7 +727,7 @@ pub(crate) fn clean_generics<'tcx>( .into_iter() .map(|(lifetime, bounds)| WherePredicate::RegionPredicate { lifetime, bounds }), ) - .chain(eq_predicates.into_iter()) + .chain(eq_predicates) .collect(), } } @@ -819,12 +805,7 @@ fn clean_ty_generics<'tcx>( { let pred = clean_predicate(*pred, cx)?; - bounds.extend( - pred.get_bounds() - .into_iter() - .flatten() - .cloned() - ); + bounds.extend(pred.get_bounds().into_iter().flatten().cloned()); if let Some(proj) = projection && let lhs = clean_projection(proj.map_bound(|p| p.projection_ty), cx, None) @@ -991,10 +972,8 @@ fn clean_proc_macro<'tcx>( cx: &mut DocContext<'tcx>, ) -> ItemKind { let attrs = cx.tcx.hir().attrs(item.hir_id()); - if kind == MacroKind::Derive && - let Some(derive_name) = attrs - .lists(sym::proc_macro_derive) - .find_map(|mi| mi.ident()) + if kind == MacroKind::Derive + && let Some(derive_name) = attrs.lists(sym::proc_macro_derive).find_map(|mi| mi.ident()) { *name = derive_name.name; } @@ -1156,7 +1135,9 @@ fn clean_fn_decl_with_args<'tcx>( hir::FnRetTy::Return(typ) => clean_ty(typ, cx), hir::FnRetTy::DefaultReturn(..) => Type::Tuple(Vec::new()), }; - if let Some(header) = header && header.is_async() { + if let Some(header) = header + && header.is_async() + { output = output.sugared_async_return_type(); } FnDecl { inputs: args, output, c_variadic: decl.c_variadic } @@ -1599,7 +1580,6 @@ fn first_non_private<'tcx>( // Absolute paths are not. We start from the parent of the item. [.., parent, leaf] => (parent.res.opt_def_id()?.as_local()?, leaf.ident), }; - let hir = cx.tcx.hir(); // First we try to get the `DefId` of the item. for child in cx.tcx.module_children_local(parent_def_id).iter().filter(move |c| c.ident == ident) @@ -1608,14 +1588,17 @@ fn first_non_private<'tcx>( continue; } - if let Some(def_id) = child.res.opt_def_id() && target_def_id == def_id { + if let Some(def_id) = child.res.opt_def_id() + && target_def_id == def_id + { let mut last_path_res = None; 'reexps: for reexp in child.reexport_chain.iter() { - if let Some(use_def_id) = reexp.id() && - let Some(local_use_def_id) = use_def_id.as_local() && - let Some(hir::Node::Item(item)) = hir.find_by_def_id(local_use_def_id) && - !item.ident.name.is_empty() && - let hir::ItemKind::Use(path, _) = item.kind + if let Some(use_def_id) = reexp.id() + && let Some(local_use_def_id) = use_def_id.as_local() + && let Some(hir::Node::Item(item)) = + cx.tcx.opt_hir_node_by_def_id(local_use_def_id) + && !item.ident.name.is_empty() + && let hir::ItemKind::Use(path, _) = item.kind { for res in &path.res { if let Res::Def(DefKind::Ctor(..), _) | Res::SelfCtor(..) = res { @@ -1626,7 +1609,8 @@ fn first_non_private<'tcx>( // We never check for "cx.render_options.document_private" // because if a re-export is not fully public, it's never // documented. - cx.tcx.local_visibility(local_use_def_id).is_public() { + cx.tcx.local_visibility(local_use_def_id).is_public() + { break 'reexps; } last_path_res = Some((path, res)); @@ -1641,7 +1625,12 @@ fn first_non_private<'tcx>( // 1. We found a public reexport. // 2. We didn't find a public reexport so it's the "end type" path. if let Some((new_path, _)) = last_path_res { - return Some(first_non_private_clean_path(cx, path, new_path.segments, new_path.span)); + return Some(first_non_private_clean_path( + cx, + path, + new_path.segments, + new_path.span, + )); } // If `last_path_res` is `None`, it can mean two things: // @@ -1816,11 +1805,8 @@ fn maybe_expand_private_type_alias<'tcx>( } _ => None, }); - if let Some(ct) = const_ { - args.insert( - param.def_id.to_def_id(), - SubstParam::Constant(clean_const(ct, cx)), - ); + if let Some(_) = const_ { + args.insert(param.def_id.to_def_id(), SubstParam::Constant); } // FIXME(const_generics_defaults) indices.consts += 1; @@ -1930,15 +1916,13 @@ fn clean_trait_object_lifetime_bound<'tcx>( // latter contrary to `clean_middle_region`. match *region { ty::ReStatic => Some(Lifetime::statik()), - ty::ReEarlyBound(region) if region.name != kw::Empty => Some(Lifetime(region.name)), - ty::ReLateBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) - if name != kw::Empty => - { + ty::ReEarlyParam(region) if region.name != kw::Empty => Some(Lifetime(region.name)), + ty::ReBound(_, ty::BoundRegion { kind: ty::BrNamed(_, name), .. }) if name != kw::Empty => { Some(Lifetime(name)) } - ty::ReEarlyBound(_) - | ty::ReLateBound(..) - | ty::ReFree(_) + ty::ReEarlyParam(_) + | ty::ReBound(..) + | ty::ReLateParam(_) | ty::ReVar(_) | ty::RePlaceholder(_) | ty::ReErased @@ -2308,7 +2292,9 @@ fn clean_middle_opaque_bounds<'tcx>( _ => return None, }; - if let Some(sized) = cx.tcx.lang_items().sized_trait() && trait_ref.def_id() == sized { + if let Some(sized) = cx.tcx.lang_items().sized_trait() + && trait_ref.def_id() == sized + { has_sized = true; return None; } @@ -2493,8 +2479,8 @@ fn clean_variant_data<'tcx>( .map(|disr| Discriminant { expr: Some(disr.body), value: disr.def_id.to_def_id() }); let kind = match variant { - hir::VariantData::Struct(..) => VariantKind::Struct(VariantStruct { - fields: variant.fields().iter().map(|x| clean_field(x, cx)).collect(), + hir::VariantData::Struct { fields, .. } => VariantKind::Struct(VariantStruct { + fields: fields.iter().map(|x| clean_field(x, cx)).collect(), }), hir::VariantData::Tuple(..) => { VariantKind::Tuple(variant.fields().iter().map(|x| clean_field(x, cx)).collect()) @@ -2534,11 +2520,12 @@ fn clean_generic_args<'tcx>( } hir::GenericArg::Lifetime(_) => GenericArg::Lifetime(Lifetime::elided()), hir::GenericArg::Type(ty) => GenericArg::Type(clean_ty(ty, cx)), - // Checking for `#[rustc_host]` on the `AnonConst` not only accounts for the case + // Checking for `is_desugared_from_effects` on the `AnonConst` not only accounts for the case // where the argument is `host` but for all possible cases (e.g., `true`, `false`). - hir::GenericArg::Const(ct) - if cx.tcx.has_attr(ct.value.def_id, sym::rustc_host) => - { + hir::GenericArg::Const(hir::ConstArg { + is_desugared_from_effects: true, + .. + }) => { return None; } hir::GenericArg::Const(ct) => GenericArg::Const(Box::new(clean_const(ct, cx))), @@ -2643,6 +2630,40 @@ fn filter_tokens_from_list( tokens } +fn filter_doc_attr_ident(ident: Symbol, is_inline: bool) -> bool { + if is_inline { + ident == sym::hidden || ident == sym::inline || ident == sym::no_inline + } else { + ident == sym::cfg + } +} + +/// Remove attributes from `normal` that should not be inherited by `use` re-export. +/// Before calling this function, make sure `normal` is a `#[doc]` attribute. +fn filter_doc_attr(normal: &mut ast::NormalAttr, is_inline: bool) { + match normal.item.args { + ast::AttrArgs::Delimited(ref mut args) => { + let tokens = filter_tokens_from_list(&args.tokens, |token| { + !matches!( + token, + TokenTree::Token( + Token { + kind: TokenKind::Ident( + ident, + _, + ), + .. + }, + _, + ) if filter_doc_attr_ident(*ident, is_inline), + ) + }); + args.tokens = TokenStream::new(tokens); + } + ast::AttrArgs::Empty | ast::AttrArgs::Eq(..) => {} + } +} + /// When inlining items, we merge their attributes (and all the reexports attributes too) with the /// final reexport. For example: /// @@ -2669,13 +2690,6 @@ fn add_without_unwanted_attributes<'hir>( is_inline: bool, import_parent: Option<DefId>, ) { - // If it's not `#[doc(inline)]`, we don't want all attributes, otherwise we keep everything. - if !is_inline { - for attr in new_attrs { - attrs.push((Cow::Borrowed(attr), import_parent)); - } - return; - } for attr in new_attrs { if matches!(attr.kind, ast::AttrKind::DocComment(..)) { attrs.push((Cow::Borrowed(attr), import_parent)); @@ -2684,34 +2698,14 @@ fn add_without_unwanted_attributes<'hir>( let mut attr = attr.clone(); match attr.kind { ast::AttrKind::Normal(ref mut normal) => { - if let [ident] = &*normal.item.path.segments && - let ident = ident.ident.name && - ident == sym::doc - { - match normal.item.args { - ast::AttrArgs::Delimited(ref mut args) => { - let tokens = - filter_tokens_from_list(&args.tokens, |token| { - !matches!( - token, - TokenTree::Token( - Token { - kind: TokenKind::Ident( - sym::hidden | sym::inline | sym::no_inline, - _, - ), - .. - }, - _, - ), - ) - }); - args.tokens = TokenStream::new(tokens); - attrs.push((Cow::Owned(attr), import_parent)); - } - ast::AttrArgs::Empty | ast::AttrArgs::Eq(..) => { - attrs.push((Cow::Owned(attr), import_parent)); - } + if let [ident] = &*normal.item.path.segments { + let ident = ident.ident.name; + if ident == sym::doc { + filter_doc_attr(normal, is_inline); + attrs.push((Cow::Owned(attr), import_parent)); + } else if ident != sym::cfg { + // If it's not a `cfg()` attribute, we keep it. + attrs.push((Cow::Owned(attr), import_parent)); } } } diff --git a/src/librustdoc/clean/render_macro_matchers.rs b/src/librustdoc/clean/render_macro_matchers.rs index 66d10f236..605f9e496 100644 --- a/src/librustdoc/clean/render_macro_matchers.rs +++ b/src/librustdoc/clean/render_macro_matchers.rs @@ -40,7 +40,7 @@ pub(super) fn render_macro_matcher(tcx: TyCtxt<'_>, matcher: &TokenTree) -> Stri printer.zerobreak(); printer.ibox(0); match matcher { - TokenTree::Delimited(_span, _delim, tts) => print_tts(&mut printer, tts), + TokenTree::Delimited(_span, _spacing, _delim, tts) => print_tts(&mut printer, tts), // Matcher which is not a Delimited is unexpected and should've failed // to compile, but we render whatever it is wrapped in parens. TokenTree::Token(..) => print_tt(&mut printer, matcher), @@ -97,7 +97,7 @@ fn print_tt(printer: &mut Printer<'_>, tt: &TokenTree) { printer.hardbreak() } } - TokenTree::Delimited(_span, delim, tts) => { + TokenTree::Delimited(_span, _spacing, delim, tts) => { let open_delim = printer.token_kind_to_string(&token::OpenDelim(*delim)); printer.word(open_delim); if !tts.is_empty() { @@ -158,7 +158,7 @@ fn print_tts(printer: &mut Printer<'_>, tts: &TokenStream) { (_, token::Pound) => (true, Pound), (_, _) => (true, Other), }, - TokenTree::Delimited(_, delim, _) => match (state, delim) { + TokenTree::Delimited(.., delim, _) => match (state, delim) { (Dollar, Delimiter::Parenthesis) => (false, DollarParen), (Pound | PoundBang, Delimiter::Bracket) => (false, Other), (Ident, Delimiter::Parenthesis | Delimiter::Bracket) => (false, Other), diff --git a/src/librustdoc/clean/simplify.rs b/src/librustdoc/clean/simplify.rs index 627f15e67..c35fb9ec7 100644 --- a/src/librustdoc/clean/simplify.rs +++ b/src/librustdoc/clean/simplify.rs @@ -146,7 +146,8 @@ pub(crate) fn move_bounds_to_generic_parameters(generics: &mut clean::Generics) }) = generics.params.iter_mut().find(|param| ¶m.name == arg) { param_bounds.extend(bounds.drain(..)); - } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = &mut pred + } else if let WherePredicate::RegionPredicate { lifetime: Lifetime(arg), bounds } = + &mut pred && let Some(GenericParamDef { kind: GenericParamDefKind::Lifetime { outlives: param_bounds }, .. diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index 88ee4e3a2..150625c6d 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -31,7 +31,7 @@ use rustc_resolve::rustdoc::{ use rustc_session::Session; use rustc_span::hygiene::MacroKind; use rustc_span::symbol::{kw, sym, Ident, Symbol}; -use rustc_span::{self, FileName, Loc, DUMMY_SP}; +use rustc_span::{FileName, Loc, DUMMY_SP}; use rustc_target::abi::VariantIdx; use rustc_target::spec::abi::Abi; @@ -345,7 +345,7 @@ pub(crate) fn rustc_span(def_id: DefId, tcx: TyCtxt<'_>) -> Span { || tcx.def_span(def_id), |local| { let hir = tcx.hir(); - hir.span_with_body(hir.local_def_id_to_hir_id(local)) + hir.span_with_body(tcx.local_def_id_to_hir_id(local)) }, )) } @@ -498,7 +498,7 @@ impl Item { } pub(crate) fn is_crate(&self) -> bool { - self.is_mod() && self.def_id().map_or(false, |did| did.is_crate_root()) + self.is_mod() && self.def_id().is_some_and(|did| did.is_crate_root()) } pub(crate) fn is_mod(&self) -> bool { self.type_() == ItemType::Module @@ -1269,8 +1269,8 @@ impl GenericBound { pub(crate) fn is_sized_bound(&self, cx: &DocContext<'_>) -> bool { use rustc_hir::TraitBoundModifier as TBM; - if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, TBM::None) = *self && - Some(trait_.def_id()) == cx.tcx.lang_items().sized_trait() + if let GenericBound::TraitBound(PolyTrait { ref trait_, .. }, TBM::None) = *self + && Some(trait_.def_id()) == cx.tcx.lang_items().sized_trait() { return true; } @@ -1623,7 +1623,7 @@ impl Type { /// functions. pub(crate) fn sugared_async_return_type(self) -> Type { if let Type::ImplTrait(mut v) = self - && let Some(GenericBound::TraitBound(PolyTrait { mut trait_, .. }, _ )) = v.pop() + && let Some(GenericBound::TraitBound(PolyTrait { mut trait_, .. }, _)) = v.pop() && let Some(segment) = trait_.segments.pop() && let GenericArgs::AngleBracketed { mut bindings, .. } = segment.args && let Some(binding) = bindings.pop() @@ -1651,6 +1651,13 @@ impl Type { } } + pub(crate) fn generic_args(&self) -> Option<&GenericArgs> { + match self { + Type::Path { path, .. } => path.generic_args(), + _ => None, + } + } + pub(crate) fn generics(&self) -> Option<Vec<&Type>> { match self { Type::Path { path, .. } => path.generics(), @@ -2191,6 +2198,10 @@ impl Path { } } + pub(crate) fn generic_args(&self) -> Option<&GenericArgs> { + self.segments.last().map(|seg| &seg.args) + } + pub(crate) fn generics(&self) -> Option<Vec<&Type>> { self.segments.last().and_then(|seg| { if let GenericArgs::AngleBracketed { ref args, .. } = seg.args { @@ -2232,6 +2243,39 @@ impl GenericArgs { GenericArgs::Parenthesized { inputs, output } => inputs.is_empty() && output.is_none(), } } + pub(crate) fn bindings<'a>(&'a self) -> Box<dyn Iterator<Item = TypeBinding> + 'a> { + match self { + GenericArgs::AngleBracketed { bindings, .. } => Box::new(bindings.iter().cloned()), + GenericArgs::Parenthesized { output, .. } => Box::new( + output + .as_ref() + .map(|ty| TypeBinding { + assoc: PathSegment { + name: sym::Output, + args: GenericArgs::AngleBracketed { + args: Vec::new().into_boxed_slice(), + bindings: ThinVec::new(), + }, + }, + kind: TypeBindingKind::Equality { term: Term::Type((**ty).clone()) }, + }) + .into_iter(), + ), + } + } +} + +impl<'a> IntoIterator for &'a GenericArgs { + type IntoIter = Box<dyn Iterator<Item = GenericArg> + 'a>; + type Item = GenericArg; + fn into_iter(self) -> Self::IntoIter { + match self { + GenericArgs::AngleBracketed { args, .. } => Box::new(args.iter().cloned()), + GenericArgs::Parenthesized { inputs, .. } => { + Box::new(inputs.iter().cloned().map(GenericArg::Type)) + } + } + } } #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -2443,7 +2487,7 @@ impl Import { } pub(crate) fn imported_item_is_doc_hidden(&self, tcx: TyCtxt<'_>) -> bool { - self.source.did.map_or(false, |did| tcx.is_doc_hidden(did)) + self.source.did.is_some_and(|did| tcx.is_doc_hidden(did)) } } @@ -2502,7 +2546,7 @@ pub(crate) enum TypeBindingKind { pub(crate) enum SubstParam { Type(Type), Lifetime(Lifetime), - Constant(Constant), + Constant, } impl SubstParam { diff --git a/src/librustdoc/clean/types/tests.rs b/src/librustdoc/clean/types/tests.rs index ee7c0068e..4befce071 100644 --- a/src/librustdoc/clean/types/tests.rs +++ b/src/librustdoc/clean/types/tests.rs @@ -1,8 +1,7 @@ use super::*; -use rustc_resolve::rustdoc::{unindent_doc_fragments, DocFragment, DocFragmentKind}; -use rustc_span::symbol::Symbol; -use rustc_span::{create_default_session_globals_then, DUMMY_SP}; +use rustc_resolve::rustdoc::{unindent_doc_fragments, DocFragmentKind}; +use rustc_span::create_default_session_globals_then; fn create_doc_fragment(s: &str) -> Vec<DocFragment> { vec![DocFragment { diff --git a/src/librustdoc/clean/utils.rs b/src/librustdoc/clean/utils.rs index 9ff00c194..bdfda07be 100644 --- a/src/librustdoc/clean/utils.rs +++ b/src/librustdoc/clean/utils.rs @@ -303,7 +303,8 @@ pub(crate) fn name_from_pat(p: &hir::Pat<'_>) -> Symbol { debug!("trying to get a name from pattern: {p:?}"); Symbol::intern(&match p.kind { - PatKind::Wild | PatKind::Struct(..) => return kw::Underscore, + // FIXME(never_patterns): does this make sense? + PatKind::Wild | PatKind::Never | 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(pats) => { @@ -438,13 +439,13 @@ fn print_const_with_custom_print_scalar<'tcx>( } pub(crate) fn is_literal_expr(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool { - if let hir::Node::Expr(expr) = tcx.hir().get(hir_id) { + if let hir::Node::Expr(expr) = tcx.hir_node(hir_id) { if let hir::ExprKind::Lit(_) = &expr.kind { return true; } - if let hir::ExprKind::Unary(hir::UnOp::Neg, expr) = &expr.kind && - let hir::ExprKind::Lit(_) = &expr.kind + if let hir::ExprKind::Unary(hir::UnOp::Neg, expr) = &expr.kind + && let hir::ExprKind::Lit(_) = &expr.kind { return true; } @@ -573,9 +574,8 @@ pub(crate) fn find_nearest_parent_module(tcx: TyCtxt<'_>, def_id: DefId) -> Opti /// This function exists because it runs on `hir::Attributes` whereas the other is a /// `clean::Attributes` method. pub(crate) fn has_doc_flag(tcx: TyCtxt<'_>, did: DefId, flag: Symbol) -> bool { - tcx.get_attrs(did, sym::doc).any(|attr| { - attr.meta_item_list().map_or(false, |l| rustc_attr::list_contains_name(&l, flag)) - }) + tcx.get_attrs(did, sym::doc) + .any(|attr| attr.meta_item_list().is_some_and(|l| rustc_attr::list_contains_name(&l, flag))) } /// A link to `doc.rust-lang.org` that includes the channel name. Use this instead of manual links @@ -641,19 +641,17 @@ pub(crate) fn inherits_doc_hidden( mut def_id: LocalDefId, stop_at: Option<LocalDefId>, ) -> bool { - let hir = tcx.hir(); while let Some(id) = tcx.opt_local_parent(def_id) { - if let Some(stop_at) = stop_at && id == stop_at { + if let Some(stop_at) = stop_at + && id == stop_at + { return false; } def_id = id; if tcx.is_doc_hidden(def_id.to_def_id()) { return true; - } else if let Some(node) = hir.find_by_def_id(def_id) && - matches!( - node, - hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }), - ) + } else if let Some(node) = tcx.opt_hir_node_by_def_id(def_id) + && matches!(node, hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(_), .. }),) { // `impl` blocks stand a bit on their own: unless they have `#[doc(hidden)]` directly // on them, they don't inherit it from the parent context. diff --git a/src/librustdoc/config.rs b/src/librustdoc/config.rs index 99aa97902..fb09d399b 100644 --- a/src/librustdoc/config.rs +++ b/src/librustdoc/config.rs @@ -15,11 +15,11 @@ use rustc_session::config::{ use rustc_session::getopts; use rustc_session::lint::Level; use rustc_session::search_paths::SearchPath; -use rustc_session::EarlyErrorHandler; +use rustc_session::EarlyDiagCtxt; use rustc_span::edition::Edition; use rustc_target::spec::TargetTriple; -use crate::core::new_handler; +use crate::core::new_dcx; use crate::externalfiles::ExternalHtml; use crate::html; use crate::html::markdown::IdMap; @@ -320,38 +320,38 @@ impl Options { /// Parses the given command-line for options. If an error message or other early-return has /// been printed, returns `Err` with the exit code. pub(crate) fn from_matches( - handler: &mut EarlyErrorHandler, + early_dcx: &mut EarlyDiagCtxt, matches: &getopts::Matches, args: Vec<String>, ) -> Result<(Options, RenderOptions), i32> { // Check for unstable options. - nightly_options::check_nightly_options(handler, matches, &opts()); + nightly_options::check_nightly_options(early_dcx, matches, &opts()); if args.is_empty() || matches.opt_present("h") || matches.opt_present("help") { crate::usage("rustdoc"); return Err(0); } else if matches.opt_present("version") { - rustc_driver::version!(&handler, "rustdoc", matches); + rustc_driver::version!(&early_dcx, "rustdoc", matches); return Err(0); } - if rustc_driver::describe_flag_categories(handler, &matches) { + if rustc_driver::describe_flag_categories(early_dcx, &matches) { return Err(0); } - let color = config::parse_color(handler, matches); + let color = config::parse_color(early_dcx, matches); let config::JsonConfig { json_rendered, json_unused_externs, .. } = - config::parse_json(handler, matches); - let error_format = config::parse_error_format(handler, matches, color, json_rendered); + config::parse_json(early_dcx, matches); + let error_format = config::parse_error_format(early_dcx, matches, color, json_rendered); let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_default(); - let codegen_options = CodegenOptions::build(handler, matches); - let unstable_opts = UnstableOptions::build(handler, matches); + let codegen_options = CodegenOptions::build(early_dcx, matches); + let unstable_opts = UnstableOptions::build(early_dcx, matches); - let diag = new_handler(error_format, None, diagnostic_width, &unstable_opts); + let dcx = new_dcx(error_format, None, diagnostic_width, &unstable_opts); // check for deprecated options - check_deprecated_options(matches, &diag); + check_deprecated_options(matches, &dcx); if matches.opt_strs("passes") == ["list"] { println!("Available passes for running rustdoc:"); @@ -391,7 +391,7 @@ impl Options { match kind.parse() { Ok(kind) => emit.push(kind), Err(()) => { - diag.err(format!("unrecognized emission type: {kind}")); + dcx.err(format!("unrecognized emission type: {kind}")); return Err(1); } } @@ -403,7 +403,7 @@ impl Options { && !matches.opt_present("show-coverage") && !nightly_options::is_unstable_enabled(matches) { - handler.early_error( + early_dcx.early_error( "the -Z unstable-options flag must be passed to enable --output-format for documentation generation (see https://github.com/rust-lang/rust/issues/76578)", ); } @@ -421,7 +421,7 @@ impl Options { let paths = match theme::load_css_paths(content) { Ok(p) => p, Err(e) => { - diag.struct_err(e).emit(); + dcx.struct_err(e).emit(); return Err(1); } }; @@ -430,7 +430,7 @@ impl Options { println!("rustdoc: [check-theme] Starting tests! (Ignoring all other arguments)"); for theme_file in to_check.iter() { print!(" - Checking \"{theme_file}\"..."); - let (success, differences) = theme::test_theme_against(theme_file, &paths, &diag); + let (success, differences) = theme::test_theme_against(theme_file, &paths, &dcx); if !differences.is_empty() || !success { println!(" FAILED"); errors += 1; @@ -447,27 +447,27 @@ impl Options { return Err(0); } - let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(handler, matches); + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches); let input = PathBuf::from(if describe_lints { "" // dummy, this won't be used } else if matches.free.is_empty() { - diag.struct_err("missing file operand").emit(); + dcx.struct_err("missing file operand").emit(); return Err(1); } else if matches.free.len() > 1 { - diag.struct_err("too many file operands").emit(); + dcx.struct_err("too many file operands").emit(); return Err(1); } else { &matches.free[0] }); let libs = - matches.opt_strs("L").iter().map(|s| SearchPath::from_cli_opt(handler, s)).collect(); - let externs = parse_externs(handler, matches, &unstable_opts); + matches.opt_strs("L").iter().map(|s| SearchPath::from_cli_opt(early_dcx, s)).collect(); + let externs = parse_externs(early_dcx, matches, &unstable_opts); let extern_html_root_urls = match parse_extern_html_roots(matches) { Ok(ex) => ex, Err(err) => { - diag.struct_err(err).emit(); + dcx.struct_err(err).emit(); return Err(1); } }; @@ -526,7 +526,7 @@ impl Options { let no_run = matches.opt_present("no-run"); if !should_test && no_run { - diag.err("the `--test` flag must be passed to enable `--no-run`"); + dcx.err("the `--test` flag must be passed to enable `--no-run`"); return Err(1); } @@ -534,7 +534,7 @@ impl Options { let output = matches.opt_str("output").map(|s| PathBuf::from(&s)); let output = match (out_dir, output) { (Some(_), Some(_)) => { - diag.struct_err("cannot use both 'out-dir' and 'output' at once").emit(); + dcx.struct_err("cannot use both 'out-dir' and 'output' at once").emit(); return Err(1); } (Some(out_dir), None) => out_dir, @@ -549,7 +549,7 @@ impl Options { if let Some(ref p) = extension_css { if !p.is_file() { - diag.struct_err("option --extend-css argument must be a file").emit(); + dcx.struct_err("option --extend-css argument must be a file").emit(); return Err(1); } } @@ -567,7 +567,7 @@ impl Options { let paths = match theme::load_css_paths(content) { Ok(p) => p, Err(e) => { - diag.struct_err(e).emit(); + dcx.struct_err(e).emit(); return Err(1); } }; @@ -576,23 +576,23 @@ impl Options { matches.opt_strs("theme").iter().map(|s| (PathBuf::from(&s), s.to_owned())) { if !theme_file.is_file() { - diag.struct_err(format!("invalid argument: \"{theme_s}\"")) + dcx.struct_err(format!("invalid argument: \"{theme_s}\"")) .help("arguments to --theme must be files") .emit(); return Err(1); } if theme_file.extension() != Some(OsStr::new("css")) { - diag.struct_err(format!("invalid argument: \"{theme_s}\"")) + dcx.struct_err(format!("invalid argument: \"{theme_s}\"")) .help("arguments to --theme must have a .css extension") .emit(); return Err(1); } - let (success, ret) = theme::test_theme_against(&theme_file, &paths, &diag); + let (success, ret) = theme::test_theme_against(&theme_file, &paths, &dcx); if !success { - diag.struct_err(format!("error loading theme file: \"{theme_s}\"")).emit(); + dcx.struct_err(format!("error loading theme file: \"{theme_s}\"")).emit(); return Err(1); } else if !ret.is_empty() { - diag.struct_warn(format!( + dcx.struct_warn(format!( "theme file \"{theme_s}\" is missing CSS rules from the default theme", )) .warn("the theme may appear incorrect when loaded") @@ -605,7 +605,7 @@ impl Options { } } - let edition = config::parse_crate_edition(handler, matches); + let edition = config::parse_crate_edition(early_dcx, matches); let mut id_map = html::markdown::IdMap::new(); let Some(external_html) = ExternalHtml::load( @@ -615,7 +615,7 @@ impl Options { &matches.opt_strs("markdown-before-content"), &matches.opt_strs("markdown-after-content"), nightly_options::match_is_nightly_build(matches), - &diag, + &dcx, &mut id_map, edition, &None, @@ -626,7 +626,7 @@ impl Options { match matches.opt_str("r").as_deref() { Some("rust") | None => {} Some(s) => { - diag.struct_err(format!("unknown input format: {s}")).emit(); + dcx.struct_err(format!("unknown input format: {s}")).emit(); return Err(1); } } @@ -634,19 +634,19 @@ impl Options { let index_page = matches.opt_str("index-page").map(|s| PathBuf::from(&s)); if let Some(ref index_page) = index_page { if !index_page.is_file() { - diag.struct_err("option `--index-page` argument must be a file").emit(); + dcx.struct_err("option `--index-page` argument must be a file").emit(); return Err(1); } } - let target = parse_target_triple(handler, matches); + let target = parse_target_triple(early_dcx, matches); let show_coverage = matches.opt_present("show-coverage"); let crate_types = match parse_crate_types_from_list(matches.opt_strs("crate-type")) { Ok(types) => types, Err(e) => { - diag.struct_err(format!("unknown crate type: {e}")).emit(); + dcx.struct_err(format!("unknown crate type: {e}")).emit(); return Err(1); } }; @@ -655,7 +655,7 @@ impl Options { Some(s) => match OutputFormat::try_from(s.as_str()) { Ok(out_fmt) => { if !out_fmt.is_json() && show_coverage { - diag.struct_err( + dcx.struct_err( "html output format isn't supported for the --show-coverage option", ) .emit(); @@ -664,7 +664,7 @@ impl Options { out_fmt } Err(e) => { - diag.struct_err(e).emit(); + dcx.struct_err(e).emit(); return Err(1); } }, @@ -709,16 +709,16 @@ impl Options { let html_no_source = matches.opt_present("html-no-source"); if generate_link_to_definition && (show_coverage || output_format != OutputFormat::Html) { - diag.struct_err( + dcx.struct_err( "--generate-link-to-definition option can only be used with HTML output format", ) .emit(); return Err(1); } - let scrape_examples_options = ScrapeExamplesOptions::new(matches, &diag)?; + let scrape_examples_options = ScrapeExamplesOptions::new(matches, &dcx)?; let with_examples = matches.opt_strs("with-examples"); - let call_locations = crate::scrape_examples::load_call_locations(with_examples, &diag)?; + let call_locations = crate::scrape_examples::load_call_locations(with_examples, &dcx)?; let unstable_features = rustc_feature::UnstableFeatures::from_environment(crate_name.as_deref()); @@ -798,17 +798,17 @@ impl Options { /// Returns `true` if the file given as `self.input` is a Markdown file. pub(crate) fn markdown_input(&self) -> bool { - self.input.extension().map_or(false, |e| e == "md" || e == "markdown") + self.input.extension().is_some_and(|e| e == "md" || e == "markdown") } } /// Prints deprecation warnings for deprecated options -fn check_deprecated_options(matches: &getopts::Matches, diag: &rustc_errors::Handler) { +fn check_deprecated_options(matches: &getopts::Matches, dcx: &rustc_errors::DiagCtxt) { let deprecated_flags = []; for &flag in deprecated_flags.iter() { if matches.opt_present(flag) { - diag.struct_warn(format!("the `{flag}` flag is deprecated")) + dcx.struct_warn(format!("the `{flag}` flag is deprecated")) .note( "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ for more information", @@ -821,7 +821,7 @@ fn check_deprecated_options(matches: &getopts::Matches, diag: &rustc_errors::Han for &flag in removed_flags.iter() { if matches.opt_present(flag) { - let mut err = diag.struct_warn(format!("the `{flag}` flag no longer functions")); + let mut err = dcx.struct_warn(format!("the `{flag}` flag no longer functions")); err.note( "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ for more information", diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 6d9f8b820..4e904ffd7 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -107,12 +107,12 @@ impl<'tcx> DocContext<'tcx> { r } - /// Like `hir().local_def_id_to_hir_id()`, but skips calling it on fake DefIds. + /// Like `tcx.local_def_id_to_hir_id()`, but skips calling it on fake DefIds. /// (This avoids a slice-index-out-of-bounds panic.) pub(crate) fn as_local_hir_id(tcx: TyCtxt<'_>, item_id: ItemId) -> Option<HirId> { match item_id { ItemId::DefId(real_id) => { - real_id.as_local().map(|def_id| tcx.hir().local_def_id_to_hir_id(def_id)) + real_id.as_local().map(|def_id| tcx.local_def_id_to_hir_id(def_id)) } // FIXME: Can this be `Some` for `Auto` or `Blanket`? _ => None, @@ -120,16 +120,16 @@ impl<'tcx> DocContext<'tcx> { } } -/// Creates a new diagnostic `Handler` that can be used to emit warnings and errors. +/// Creates a new `DiagCtxt` that can be used to emit warnings and errors. /// /// If the given `error_format` is `ErrorOutputType::Json` and no `SourceMap` is given, a new one -/// will be created for the handler. -pub(crate) fn new_handler( +/// will be created for the `DiagCtxt`. +pub(crate) fn new_dcx( error_format: ErrorOutputType, source_map: Option<Lrc<source_map::SourceMap>>, diagnostic_width: Option<usize>, unstable_opts: &UnstableOptions, -) -> rustc_errors::Handler { +) -> rustc_errors::DiagCtxt { let fallback_bundle = rustc_errors::fallback_fluent_bundle( rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false, @@ -169,8 +169,7 @@ pub(crate) fn new_handler( } }; - rustc_errors::Handler::with_emitter(emitter) - .with_flags(unstable_opts.diagnostic_handler_flags(true)) + rustc_errors::DiagCtxt::with_emitter(emitter).with_flags(unstable_opts.dcx_flags(true)) } /// Parse, resolve, and typecheck the given crate. @@ -380,13 +379,15 @@ pub(crate) fn run_global_ctxt( crate::lint::MISSING_CRATE_LEVEL_DOCS, DocContext::as_local_hir_id(tcx, krate.module.item_id).unwrap(), "no documentation found for this crate's top-level module", - |lint| lint.help(help), + |lint| { + lint.help(help); + }, ); } - fn report_deprecated_attr(name: &str, diag: &rustc_errors::Handler, sp: Span) { + fn report_deprecated_attr(name: &str, dcx: &rustc_errors::DiagCtxt, sp: Span) { let mut msg = - diag.struct_span_warn(sp, format!("the `#![doc({name})]` attribute is deprecated")); + dcx.struct_span_warn(sp, format!("the `#![doc({name})]` attribute is deprecated")); msg.note( "see issue #44136 <https://github.com/rust-lang/rust/issues/44136> \ for more information", @@ -406,19 +407,19 @@ pub(crate) fn run_global_ctxt( // Process all of the crate attributes, extracting plugin metadata along // with the passes which we are supposed to run. for attr in krate.module.attrs.lists(sym::doc) { - let diag = ctxt.sess().diagnostic(); + let dcx = ctxt.sess().dcx(); let name = attr.name_or_empty(); // `plugins = "..."`, `no_default_passes`, and `passes = "..."` have no effect if attr.is_word() && name == sym::no_default_passes { - report_deprecated_attr("no_default_passes", diag, attr.span()); + report_deprecated_attr("no_default_passes", dcx, attr.span()); } else if attr.value_str().is_some() { match name { sym::passes => { - report_deprecated_attr("passes = \"...\"", diag, attr.span()); + report_deprecated_attr("passes = \"...\"", dcx, attr.span()); } sym::plugins => { - report_deprecated_attr("plugins = \"...\"", diag, attr.span()); + report_deprecated_attr("plugins = \"...\"", dcx, attr.span()); } _ => (), } @@ -446,7 +447,7 @@ pub(crate) fn run_global_ctxt( tcx.sess.time("check_lint_expectations", || tcx.check_expectations(Some(sym::rustdoc))); - if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() { + if tcx.sess.dcx().has_errors_or_lint_errors().is_some() { rustc_errors::FatalError.raise(); } diff --git a/src/librustdoc/docfs.rs b/src/librustdoc/docfs.rs index 82c1a5039..1f7abdfc3 100644 --- a/src/librustdoc/docfs.rs +++ b/src/librustdoc/docfs.rs @@ -12,7 +12,6 @@ use std::cmp::max; use std::fs; use std::io; use std::path::{Path, PathBuf}; -use std::string::ToString; use std::sync::mpsc::Sender; use std::thread::available_parallelism; use threadpool::ThreadPool; diff --git a/src/librustdoc/doctest.rs b/src/librustdoc/doctest.rs index 241286580..c74f2ecb0 100644 --- a/src/librustdoc/doctest.rs +++ b/src/librustdoc/doctest.rs @@ -127,17 +127,17 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { options, false, opts, - Some(compiler.session().parse_sess.clone_source_map()), + Some(compiler.sess.parse_sess.clone_source_map()), None, enable_per_target_ignores, ); let mut hir_collector = HirCollector { - sess: compiler.session(), + sess: &compiler.sess, collector: &mut collector, map: tcx.hir(), codes: ErrorCodes::from( - compiler.session().opts.unstable_features.is_nightly_build(), + compiler.sess.opts.unstable_features.is_nightly_build(), ), tcx, }; @@ -150,7 +150,7 @@ pub(crate) fn run(options: RustdocOptions) -> Result<(), ErrorGuaranteed> { collector }); - if compiler.session().diagnostic().has_errors_or_lint_errors().is_some() { + if compiler.sess.dcx().has_errors_or_lint_errors().is_some() { FatalError.raise(); } @@ -558,7 +558,7 @@ pub(crate) fn make_test( let result = rustc_driver::catch_fatal_errors(|| { rustc_span::create_session_if_not_set_then(edition, |_| { use rustc_errors::emitter::{Emitter, EmitterWriter}; - use rustc_errors::Handler; + use rustc_errors::DiagCtxt; use rustc_parse::parser::ForceCollect; use rustc_span::source_map::FilePathMapping; @@ -579,8 +579,8 @@ pub(crate) fn make_test( let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser - let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); - let sess = ParseSess::with_span_handler(handler, sm); + let dcx = DiagCtxt::with_emitter(Box::new(emitter)).disable_warnings(); + let sess = ParseSess::with_dcx(dcx, sm); let mut found_main = false; let mut found_extern_crate = crate_name.is_none(); @@ -597,15 +597,15 @@ pub(crate) fn make_test( loop { match parser.parse_item(ForceCollect::No) { Ok(Some(item)) => { - if !found_main && - let ast::ItemKind::Fn(..) = item.kind && - item.ident.name == sym::main + if !found_main + && let ast::ItemKind::Fn(..) = item.kind + && item.ident.name == sym::main { found_main = true; } - if !found_extern_crate && - let ast::ItemKind::ExternCrate(original) = item.kind + if !found_extern_crate + && let ast::ItemKind::ExternCrate(original) = item.kind { // This code will never be reached if `crate_name` is none because // `found_extern_crate` is initialized to `true` if it is none. @@ -638,10 +638,10 @@ pub(crate) fn make_test( } // Reset errors so that they won't be reported as compiler bugs when dropping the - // handler. Any errors in the tests will be reported when the test file is compiled, + // dcx. Any errors in the tests will be reported when the test file is compiled, // Note that we still need to cancel the errors above otherwise `DiagnosticBuilder` // will panic on drop. - sess.span_diagnostic.reset_err_count(); + sess.dcx.reset_err_count(); (found_main, found_extern_crate, found_macro) }) @@ -740,7 +740,7 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool { rustc_driver::catch_fatal_errors(|| { rustc_span::create_session_if_not_set_then(edition, |_| { use rustc_errors::emitter::EmitterWriter; - use rustc_errors::Handler; + use rustc_errors::DiagCtxt; use rustc_span::source_map::FilePathMapping; let filename = FileName::anon_source_code(source); @@ -754,8 +754,8 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool { let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle); - let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); - let sess = ParseSess::with_span_handler(handler, sm); + let dcx = DiagCtxt::with_emitter(Box::new(emitter)).disable_warnings(); + let sess = ParseSess::with_dcx(dcx, sm); let mut parser = match maybe_new_parser_from_source_str(&sess, filename, source.to_owned()) { Ok(p) => p, @@ -957,10 +957,10 @@ impl Collector { fn get_filename(&self) -> FileName { if let Some(ref source_map) = self.source_map { let filename = source_map.span_to_filename(self.position); - if let FileName::Real(ref filename) = filename && - let Ok(cur_dir) = env::current_dir() && - let Some(local_path) = filename.local_path() && - let Ok(path) = local_path.strip_prefix(&cur_dir) + if let FileName::Real(ref filename) = filename + && let Ok(cur_dir) = env::current_dir() + && let Some(local_path) = filename.local_path() + && let Ok(path) = local_path.strip_prefix(&cur_dir) { return path.to_owned().into(); } @@ -1207,7 +1207,7 @@ impl<'a, 'hir, 'tcx> HirCollector<'a, 'hir, 'tcx> { sp: Span, nested: F, ) { - let ast_attrs = self.tcx.hir().attrs(self.tcx.hir().local_def_id_to_hir_id(def_id)); + let ast_attrs = self.tcx.hir().attrs(self.tcx.local_def_id_to_hir_id(def_id)); if let Some(ref cfg) = ast_attrs.cfg(self.tcx, &FxHashSet::default()) { if !cfg.matches(&self.sess.parse_sess, Some(self.tcx.features())) { return; diff --git a/src/librustdoc/externalfiles.rs b/src/librustdoc/externalfiles.rs index b34b69b1f..8bc0cdf3a 100644 --- a/src/librustdoc/externalfiles.rs +++ b/src/librustdoc/externalfiles.rs @@ -27,15 +27,15 @@ impl ExternalHtml { md_before_content: &[String], md_after_content: &[String], nightly_build: bool, - diag: &rustc_errors::Handler, + dcx: &rustc_errors::DiagCtxt, id_map: &mut IdMap, edition: Edition, playground: &Option<Playground>, ) -> Option<ExternalHtml> { let codes = ErrorCodes::from(nightly_build); - let ih = load_external_files(in_header, diag)?; - let bc = load_external_files(before_content, diag)?; - let m_bc = load_external_files(md_before_content, diag)?; + let ih = load_external_files(in_header, dcx)?; + let bc = load_external_files(before_content, dcx)?; + let m_bc = load_external_files(md_before_content, dcx)?; let bc = format!( "{bc}{}", Markdown { @@ -51,8 +51,8 @@ impl ExternalHtml { } .into_string() ); - let ac = load_external_files(after_content, diag)?; - let m_ac = load_external_files(md_after_content, diag)?; + let ac = load_external_files(after_content, dcx)?; + let m_ac = load_external_files(md_after_content, dcx)?; let ac = format!( "{ac}{}", Markdown { @@ -79,13 +79,13 @@ pub(crate) enum LoadStringError { pub(crate) fn load_string<P: AsRef<Path>>( file_path: P, - diag: &rustc_errors::Handler, + dcx: &rustc_errors::DiagCtxt, ) -> Result<String, LoadStringError> { let file_path = file_path.as_ref(); let contents = match fs::read(file_path) { Ok(bytes) => bytes, Err(e) => { - diag.struct_err(format!( + dcx.struct_err(format!( "error reading `{file_path}`: {e}", file_path = file_path.display() )) @@ -96,16 +96,16 @@ pub(crate) fn load_string<P: AsRef<Path>>( match str::from_utf8(&contents) { Ok(s) => Ok(s.to_string()), Err(_) => { - diag.struct_err(format!("error reading `{}`: not UTF-8", file_path.display())).emit(); + dcx.struct_err(format!("error reading `{}`: not UTF-8", file_path.display())).emit(); Err(LoadStringError::BadUtf8) } } } -fn load_external_files(names: &[String], diag: &rustc_errors::Handler) -> Option<String> { +fn load_external_files(names: &[String], dcx: &rustc_errors::DiagCtxt) -> Option<String> { let mut out = String::new(); for name in names { - let Ok(s) = load_string(name, diag) else { return None }; + let Ok(s) = load_string(name, dcx) else { return None }; out.push_str(&s); out.push('\n'); } diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index abff77253..9802097ea 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -230,14 +230,14 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // If the impl is from a masked crate or references something from a // masked crate then remove it completely. - if let clean::ImplItem(ref i) = *item.kind && - (self.cache.masked_crates.contains(&item.item_id.krate()) + if let clean::ImplItem(ref i) = *item.kind + && (self.cache.masked_crates.contains(&item.item_id.krate()) || i.trait_ .as_ref() - .map_or(false, |t| is_from_private_dep(self.tcx, self.cache, t.def_id())) + .is_some_and(|t| is_from_private_dep(self.tcx, self.cache, t.def_id())) || i.for_ .def_id(self.cache) - .map_or(false, |d| is_from_private_dep(self.tcx, self.cache, d))) + .is_some_and(|d| is_from_private_dep(self.tcx, self.cache, d))) { return None; } @@ -249,9 +249,9 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { } // Collect all the implementors of traits. - if let clean::ImplItem(ref i) = *item.kind && - let Some(trait_) = &i.trait_ && - !i.kind.is_blanket() + if let clean::ImplItem(ref i) = *item.kind + && let Some(trait_) = &i.trait_ + && !i.kind.is_blanket() { self.cache .implementors @@ -264,8 +264,9 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { if let Some(s) = item.name.or_else(|| { if item.is_stripped() { None - } else if let clean::ImportItem(ref i) = *item.kind && - let clean::ImportKind::Simple(s) = i.kind { + } else if let clean::ImportItem(ref i) = *item.kind + && let clean::ImportKind::Simple(s) = i.kind + { Some(s) } else { None @@ -278,7 +279,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { .cache .parent_stack .last() - .map_or(false, |parent| parent.is_trait_impl()) => + .is_some_and(|parent| parent.is_trait_impl()) => { // skip associated items in trait impls ((None, None), false) @@ -340,7 +341,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { // A crate has a module at its root, containing all items, // which should not be indexed. The crate-item itself is // inserted later on when serializing the search-index. - if item.item_id.as_def_id().map_or(false, |idx| !idx.is_crate_root()) + if item.item_id.as_def_id().is_some_and(|idx| !idx.is_crate_root()) && let ty = item.type_() && (ty != ItemType::StructField || u16::from_str_radix(s.as_str(), 10).is_err()) @@ -357,7 +358,9 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { desc, parent, parent_idx: None, - impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = self.cache.parent_stack.last() { + impl_id: if let Some(ParentStackItem::Impl { item_id, .. }) = + self.cache.parent_stack.last() + { item_id.as_def_id() } else { None @@ -366,6 +369,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { &item, self.tcx, clean_impl_generics(self.cache.parent_stack.last()).as_ref(), + parent, self.cache, ), aliases: item.attrs.get_doc_aliases(), @@ -493,9 +497,11 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { clean::Type::Path { ref path } | clean::BorrowedRef { type_: box clean::Type::Path { ref path }, .. } => { dids.insert(path.def_id()); - if let Some(generics) = path.generics() && - let ty::Adt(adt, _) = self.tcx.type_of(path.def_id()).instantiate_identity().kind() && - adt.is_fundamental() { + if let Some(generics) = path.generics() + && let ty::Adt(adt, _) = + self.tcx.type_of(path.def_id()).instantiate_identity().kind() + && adt.is_fundamental() + { for ty in generics { if let Some(did) = ty.def_id(self.cache) { dids.insert(did); diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index def3a90c8..e80da46ad 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -16,6 +16,13 @@ use crate::clean; /// Consequently, every change to this type should be synchronized to /// the `itemTypes` mapping table in `html/static/js/search.js`. /// +/// The search engine in search.js also uses item type numbers as a tie breaker when +/// sorting results. Keywords and primitives are given first because we want them to be easily +/// found by new users who don't know about advanced features like type filters. The rest are +/// mostly in an arbitrary order, but it's easier to test the search engine when +/// it's deterministic, and these are strictly finer-grained than language namespaces, so +/// using the path and the item type together to sort ensures that search sorting is stable. +/// /// In addition, code in `html::render` uses this enum to generate CSS classes, page prefixes, and /// module headings. If you are adding to this enum and want to ensure that the sidebar also prints /// a heading, edit the listing in `html/render.rs`, function `sidebar_module`. This uses an @@ -23,32 +30,34 @@ use crate::clean; #[derive(Copy, PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] #[repr(u8)] pub(crate) enum ItemType { - Module = 0, - ExternCrate = 1, - Import = 2, - Struct = 3, - Enum = 4, - Function = 5, - TypeAlias = 6, - Static = 7, - Trait = 8, - Impl = 9, - TyMethod = 10, - Method = 11, - StructField = 12, - Variant = 13, - Macro = 14, - Primitive = 15, - AssocType = 16, - Constant = 17, - AssocConst = 18, - Union = 19, - ForeignType = 20, - Keyword = 21, + Keyword = 0, + Primitive = 1, + Module = 2, + ExternCrate = 3, + Import = 4, + Struct = 5, + Enum = 6, + Function = 7, + TypeAlias = 8, + Static = 9, + Trait = 10, + Impl = 11, + TyMethod = 12, + Method = 13, + StructField = 14, + Variant = 15, + Macro = 16, + AssocType = 17, + Constant = 18, + AssocConst = 19, + Union = 20, + ForeignType = 21, OpaqueTy = 22, ProcAttribute = 23, ProcDerive = 24, TraitAlias = 25, + // This number is reserved for use in JavaScript + // Generic = 26, } impl Serialize for ItemType { @@ -140,8 +149,7 @@ impl From<DefKind> for ItemType { | DefKind::LifetimeParam | DefKind::GlobalAsm | DefKind::Impl { .. } - | DefKind::Closure - | DefKind::Coroutine => Self::ForeignType, + | DefKind::Closure => Self::ForeignType, } } } diff --git a/src/librustdoc/formats/mod.rs b/src/librustdoc/formats/mod.rs index e607a16ad..0056fb485 100644 --- a/src/librustdoc/formats/mod.rs +++ b/src/librustdoc/formats/mod.rs @@ -9,21 +9,6 @@ pub(crate) use renderer::{run_format, FormatRenderer}; use crate::clean::{self, ItemId}; use crate::html::render::Context; -/// Specifies whether rendering directly implemented trait items or ones from a certain Deref -/// impl. -pub(crate) enum AssocItemRender<'a> { - All, - DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool }, -} - -/// For different handling of associated items from the Deref target of a type rather than the type -/// itself. -#[derive(Copy, Clone, PartialEq)] -pub(crate) enum RenderMode { - Normal, - ForDeref { mut_: bool }, -} - /// Metadata about implementations for a type or trait. #[derive(Clone, Debug)] pub(crate) struct Impl { diff --git a/src/librustdoc/formats/renderer.rs b/src/librustdoc/formats/renderer.rs index c49f1a4d3..2535668b8 100644 --- a/src/librustdoc/formats/renderer.rs +++ b/src/librustdoc/formats/renderer.rs @@ -1,5 +1,4 @@ use rustc_middle::ty::TyCtxt; -use rustc_span::Symbol; use crate::clean; use crate::config::RenderOptions; @@ -68,7 +67,6 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>( // Render the crate documentation let mut work = vec![(format_renderer.make_child_renderer(), krate.module)]; - let unknown = Symbol::intern("<unknown item>"); while let Some((mut cx, item)) = work.pop() { if item.is_mod() && T::RUN_ON_MODULE { // modules are special because they add a namespace. We also need to @@ -90,8 +88,10 @@ pub(crate) fn run_format<'tcx, T: FormatRenderer<'tcx>>( cx.mod_item_out()?; // FIXME: checking `item.name.is_some()` is very implicit and leads to lots of special // cases. Use an explicit match instead. - } else if item.name.is_some() && !item.is_extern_crate() { - prof.generic_activity_with_arg("render_item", item.name.unwrap_or(unknown).as_str()) + } else if let Some(item_name) = item.name + && !item.is_extern_crate() + { + prof.generic_activity_with_arg("render_item", item_name.as_str()) .run(|| cx.item(item))?; } } diff --git a/src/librustdoc/html/escape.rs b/src/librustdoc/html/escape.rs index 4a19d0a44..ea4b573ae 100644 --- a/src/librustdoc/html/escape.rs +++ b/src/librustdoc/html/escape.rs @@ -38,3 +38,39 @@ impl<'a> fmt::Display for Escape<'a> { Ok(()) } } + +/// Wrapper struct which will emit the HTML-escaped version of the contained +/// string when passed to a format string. +/// +/// This is only safe to use for text nodes. If you need your output to be +/// safely contained in an attribute, use [`Escape`]. If you don't know the +/// difference, use [`Escape`]. +pub(crate) struct EscapeBodyText<'a>(pub &'a str); + +impl<'a> fmt::Display for EscapeBodyText<'a> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + // Because the internet is always right, turns out there's not that many + // characters to escape: http://stackoverflow.com/questions/7381974 + let EscapeBodyText(s) = *self; + let pile_o_bits = s; + let mut last = 0; + for (i, ch) in s.char_indices() { + let s = match ch { + '>' => ">", + '<' => "<", + '&' => "&", + _ => continue, + }; + fmt.write_str(&pile_o_bits[last..i])?; + fmt.write_str(s)?; + // NOTE: we only expect single byte characters here - which is fine as long as we + // only match single byte characters + last = i + 1; + } + + if last < s.len() { + fmt.write_str(&pile_o_bits[last..])?; + } + Ok(()) + } +} diff --git a/src/librustdoc/html/format.rs b/src/librustdoc/html/format.rs index 29fd880af..a9c0ab557 100644 --- a/src/librustdoc/html/format.rs +++ b/src/librustdoc/html/format.rs @@ -367,7 +367,7 @@ pub(crate) fn print_where_clause<'a, 'tcx: 'a>( if ending == Ending::Newline { let mut clause = " ".repeat(indent.saturating_sub(1)); - write!(clause, "<span class=\"where fmt-newline\">where{where_preds},</span>")?; + write!(clause, "<div class=\"where\">where{where_preds},</div>")?; clause } else { // insert a newline after a single space but before multiple spaces at the start @@ -1166,13 +1166,17 @@ fn fmt_type<'cx>( // we need to surround them with angle brackets in some cases (e.g. `<dyn …>::P`). if f.alternate() { - if let Some(trait_) = trait_ && should_show_cast { + if let Some(trait_) = trait_ + && should_show_cast + { write!(f, "<{:#} as {:#}>::", self_type.print(cx), trait_.print(cx))? } else { write!(f, "{:#}::", self_type.print(cx))? } } else { - if let Some(trait_) = trait_ && should_show_cast { + if let Some(trait_) = trait_ + && should_show_cast + { write!(f, "<{} as {}>::", self_type.print(cx), trait_.print(cx))? } else { write!(f, "{}::", self_type.print(cx))? @@ -1268,16 +1272,23 @@ impl clean::Impl { write!(f, " for ")?; } - if let clean::Type::Tuple(types) = &self.for_ && - let [clean::Type::Generic(name)] = &types[..] && - (self.kind.is_fake_variadic() || self.kind.is_auto()) + if let clean::Type::Tuple(types) = &self.for_ + && let [clean::Type::Generic(name)] = &types[..] + && (self.kind.is_fake_variadic() || self.kind.is_auto()) { // Hardcoded anchor library/core/src/primitive_docs.rs // Link should match `# Trait implementations` - primitive_link_fragment(f, PrimitiveType::Tuple, format_args!("({name}₁, {name}₂, …, {name}ₙ)"), "#trait-implementations-1", cx)?; - } else if let clean::BareFunction(bare_fn) = &self.for_ && - let [clean::Argument { type_: clean::Type::Generic(name), .. }] = &bare_fn.decl.inputs.values[..] && - (self.kind.is_fake_variadic() || self.kind.is_auto()) + primitive_link_fragment( + f, + PrimitiveType::Tuple, + format_args!("({name}₁, {name}₂, …, {name}ₙ)"), + "#trait-implementations-1", + cx, + )?; + } else if let clean::BareFunction(bare_fn) = &self.for_ + && let [clean::Argument { type_: clean::Type::Generic(name), .. }] = + &bare_fn.decl.inputs.values[..] + && (self.kind.is_fake_variadic() || self.kind.is_auto()) { // Hardcoded anchor library/core/src/primitive_docs.rs // Link should match `# Trait implementations` @@ -1286,22 +1297,18 @@ impl clean::Impl { let unsafety = bare_fn.unsafety.print_with_space(); let abi = print_abi_with_space(bare_fn.abi); if f.alternate() { - write!( - f, - "{hrtb:#}{unsafety}{abi:#}", - )?; + write!(f, "{hrtb:#}{unsafety}{abi:#}",)?; } else { - write!( - f, - "{hrtb}{unsafety}{abi}", - )?; + write!(f, "{hrtb}{unsafety}{abi}",)?; } - let ellipsis = if bare_fn.decl.c_variadic { - ", ..." - } else { - "" - }; - primitive_link_fragment(f, PrimitiveType::Tuple, format_args!("fn ({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), "#trait-implementations-1", cx)?; + let ellipsis = if bare_fn.decl.c_variadic { ", ..." } else { "" }; + primitive_link_fragment( + f, + PrimitiveType::Tuple, + format_args!("fn({name}₁, {name}₂, …, {name}ₙ{ellipsis})"), + "#trait-implementations-1", + cx, + )?; // Write output. if !bare_fn.decl.output.is_unit() { write!(f, " -> ")?; @@ -1447,7 +1454,9 @@ impl clean::FnDecl { let amp = if f.alternate() { "&" } else { "&" }; write!(f, "(")?; - if let Some(n) = line_wrapping_indent && !self.inputs.values.is_empty() { + if let Some(n) = line_wrapping_indent + && !self.inputs.values.is_empty() + { write!(f, "\n{}", Indent(n + 4))?; } for (i, input) in self.inputs.values.iter().enumerate() { diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index d8e36139a..1cdc792a8 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -6,7 +6,7 @@ //! Use the `render_with_highlighting` to highlight some rust code. use crate::clean::PrimitiveType; -use crate::html::escape::Escape; +use crate::html::escape::EscapeBodyText; use crate::html::render::{Context, LinkFromSrc}; use std::collections::VecDeque; @@ -185,22 +185,30 @@ impl<'a, 'tcx, F: Write> TokenHandler<'a, 'tcx, F> { if self.pending_elems.is_empty() { return false; } - if let Some((_, parent_class)) = self.closing_tags.last() && - can_merge(current_class, Some(*parent_class), "") + if let Some((_, parent_class)) = self.closing_tags.last() + && can_merge(current_class, Some(*parent_class), "") { for (text, class) in self.pending_elems.iter() { - string(self.out, Escape(text), *class, &self.href_context, false); + string(self.out, EscapeBodyText(text), *class, &self.href_context, false); } } else { // We only want to "open" the tag ourselves if we have more than one pending and if the // current parent tag is not the same as our pending content. - let close_tag = if self.pending_elems.len() > 1 && let Some(current_class) = current_class { + let close_tag = if self.pending_elems.len() > 1 + && let Some(current_class) = current_class + { Some(enter_span(self.out, current_class, &self.href_context)) } else { None }; for (text, class) in self.pending_elems.iter() { - string(self.out, Escape(text), *class, &self.href_context, close_tag.is_none()); + string( + self.out, + EscapeBodyText(text), + *class, + &self.href_context, + close_tag.is_none(), + ); } if let Some(close_tag) = close_tag { exit_span(self.out, close_tag); @@ -260,10 +268,12 @@ pub(super) fn write_code( Highlight::Token { text, class } => { // If we received a `ExitSpan` event and then have a non-compatible `Class`, we // need to close the `<span>`. - let need_current_class_update = if let Some(pending) = token_handler.pending_exit_span && - !can_merge(Some(pending), class, text) { - token_handler.handle_exit_span(); - true + let need_current_class_update = if let Some(pending) = + token_handler.pending_exit_span + && !can_merge(Some(pending), class, text) + { + token_handler.handle_exit_span(); + true // If the two `Class` are different, time to flush the current content and start // a new one. } else if !can_merge(token_handler.current_class, class, text) { @@ -293,7 +303,8 @@ pub(super) fn write_code( } } if should_add { - let closing_tag = enter_span(token_handler.out, class, &token_handler.href_context); + let closing_tag = + enter_span(token_handler.out, class, &token_handler.href_context); token_handler.closing_tags.push((closing_tag, class)); } @@ -302,8 +313,14 @@ pub(super) fn write_code( } Highlight::ExitSpan => { token_handler.current_class = None; - token_handler.pending_exit_span = - Some(token_handler.closing_tags.last().as_ref().expect("ExitSpan without EnterSpan").1); + token_handler.pending_exit_span = Some( + token_handler + .closing_tags + .last() + .as_ref() + .expect("ExitSpan without EnterSpan") + .1, + ); } }; }); @@ -466,7 +483,9 @@ impl<'a> PeekIter<'a> { } /// Returns the next item after the current one. It doesn't interfere with `peek_next` output. fn peek(&mut self) -> Option<&(TokenKind, &'a str)> { - if self.stored.is_empty() && let Some(next) = self.iter.next() { + if self.stored.is_empty() + && let Some(next) = self.iter.next() + { self.stored.push_back(next); } self.stored.front() diff --git a/src/librustdoc/html/highlight/fixtures/dos_line.html b/src/librustdoc/html/highlight/fixtures/dos_line.html index 30b50ca7c..b98e67125 100644 --- a/src/librustdoc/html/highlight/fixtures/dos_line.html +++ b/src/librustdoc/html/highlight/fixtures/dos_line.html @@ -1,3 +1,3 @@ <span class="kw">pub fn </span>foo() { -<span class="macro">println!</span>(<span class="string">"foo"</span>); +<span class="macro">println!</span>(<span class="string">"foo"</span>); } diff --git a/src/librustdoc/html/highlight/fixtures/sample.html b/src/librustdoc/html/highlight/fixtures/sample.html index fced2eacd..aa735e815 100644 --- a/src/librustdoc/html/highlight/fixtures/sample.html +++ b/src/librustdoc/html/highlight/fixtures/sample.html @@ -8,12 +8,12 @@ .lifetime { color: #B76514; } .question-mark { color: #ff9011; } </style> -<pre><code><span class="attr">#![crate_type = <span class="string">"lib"</span>] +<pre><code><span class="attr">#![crate_type = <span class="string">"lib"</span>] </span><span class="kw">use </span>std::path::{Path, PathBuf}; -<span class="attr">#[cfg(target_os = <span class="string">"linux"</span>)] -#[cfg(target_os = <span class="string">"windows"</span>)] +<span class="attr">#[cfg(target_os = <span class="string">"linux"</span>)] +#[cfg(target_os = <span class="string">"windows"</span>)] </span><span class="kw">fn </span>main() -> () { <span class="kw">let </span>foo = <span class="bool-val">true </span>&& <span class="bool-val">false </span>|| <span class="bool-val">true</span>; <span class="kw">let _</span>: <span class="kw-2">*const </span>() = <span class="number">0</span>; @@ -22,7 +22,7 @@ <span class="kw">let _ </span>= <span class="kw-2">*</span>foo; <span class="macro">mac!</span>(foo, <span class="kw-2">&mut </span>bar); <span class="macro">assert!</span>(<span class="self">self</span>.length < N && index <= <span class="self">self</span>.length); - ::std::env::var(<span class="string">"gateau"</span>).is_ok(); + ::std::env::var(<span class="string">"gateau"</span>).is_ok(); <span class="attr">#[rustfmt::skip] </span><span class="kw">let </span>s:std::path::PathBuf = std::path::PathBuf::new(); <span class="kw">let </span><span class="kw-2">mut </span>s = String::new(); diff --git a/src/librustdoc/html/markdown.rs b/src/librustdoc/html/markdown.rs index 2807dfed0..642265f5f 100644 --- a/src/librustdoc/html/markdown.rs +++ b/src/librustdoc/html/markdown.rs @@ -27,7 +27,7 @@ //! ``` use rustc_data_structures::fx::FxHashMap; -use rustc_errors::{DiagnosticMessage, SubdiagnosticMessage}; +use rustc_errors::{DiagnosticBuilder, DiagnosticMessage}; use rustc_hir::def_id::DefId; use rustc_middle::ty::TyCtxt; pub(crate) use rustc_resolve::rustdoc::main_body_opts; @@ -234,10 +234,6 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'_, 'a, I> { fn next(&mut self) -> Option<Self::Item> { let event = self.inner.next(); - let compile_fail; - let should_panic; - let ignore; - let edition; let Some(Event::Start(Tag::CodeBlock(kind))) = event else { return event; }; @@ -253,49 +249,44 @@ 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, - self.custom_code_classes_in_docs, - ); - if !parse_result.rust { - let added_classes = parse_result.added_classes; - let lang_string = if let Some(lang) = parse_result.unknown.first() { - format!("language-{}", lang) - } else { - String::new() - }; - let whitespace = if added_classes.is_empty() { "" } else { " " }; - return Some(Event::Html( - format!( - "<div class=\"example-wrap\">\ + let LangString { added_classes, compile_fail, should_panic, ignore, edition, .. } = + match kind { + CodeBlockKind::Fenced(ref lang) => { + let parse_result = LangString::parse_without_check( + lang, + self.check_error_codes, + false, + self.custom_code_classes_in_docs, + ); + if !parse_result.rust { + let added_classes = parse_result.added_classes; + let lang_string = if let Some(lang) = parse_result.unknown.first() { + format!("language-{}", lang) + } else { + String::new() + }; + let whitespace = if added_classes.is_empty() { "" } else { " " }; + return Some(Event::Html( + format!( + "<div class=\"example-wrap\">\ <pre class=\"{lang_string}{whitespace}{added_classes}\">\ <code>{text}</code>\ </pre>\ </div>", - added_classes = added_classes.join(" "), - text = Escape(&original_text), - ) - .into(), - )); + added_classes = added_classes.join(" "), + text = Escape(&original_text), + ) + .into(), + )); + } + parse_result } - parse_result - } - CodeBlockKind::Indented => Default::default(), - }; + CodeBlockKind::Indented => Default::default(), + }; - let added_classes = parse_result.added_classes; let lines = original_text.lines().filter_map(|l| map_line(l).for_html()); let text = lines.intersperse("\n".into()).collect::<String>(); - compile_fail = parse_result.compile_fail; - should_panic = parse_result.should_panic; - ignore = parse_result.ignore; - edition = parse_result.edition; - let explicit_edition = edition.is_some(); let edition = edition.unwrap_or(self.edition); @@ -841,10 +832,10 @@ impl<'tcx> ExtraInfo<'tcx> { if let Some(def_id) = self.def_id.as_local() { self.tcx.struct_span_lint_hir( crate::lint::INVALID_CODEBLOCK_ATTRIBUTES, - self.tcx.hir().local_def_id_to_hir_id(def_id), + self.tcx.local_def_id_to_hir_id(def_id), self.sp, msg, - |l| l, + |_| {}, ); } } @@ -852,15 +843,15 @@ impl<'tcx> ExtraInfo<'tcx> { fn error_invalid_codeblock_attr_with_help( &self, msg: impl Into<DiagnosticMessage>, - help: impl Into<SubdiagnosticMessage>, + f: impl for<'a, 'b> FnOnce(&'b mut DiagnosticBuilder<'a, ()>), ) { if let Some(def_id) = self.def_id.as_local() { self.tcx.struct_span_lint_hir( crate::lint::INVALID_CODEBLOCK_ATTRIBUTES, - self.tcx.hir().local_def_id_to_hir_id(def_id), + self.tcx.local_def_id_to_hir_id(def_id), self.sp, msg, - |lint| lint.help(help), + f, ); } } @@ -1119,10 +1110,10 @@ impl<'a, 'tcx> TagIterator<'a, 'tcx> { return None; } let indices = self.parse_string(pos)?; - if let Some((_, c)) = self.inner.peek().copied() && - c != '{' && - !is_separator(c) && - c != '(' + if let Some((_, c)) = self.inner.peek().copied() + && c != '{' + && !is_separator(c) + && c != '(' { self.emit_error(format!("expected ` `, `{{` or `,` after `\"`, found `{c}`")); return None; @@ -1294,6 +1285,21 @@ impl LangString { data.edition = x[7..].parse::<Edition>().ok(); } LangStringToken::LangToken(x) + if x.starts_with("rust") && x[4..].parse::<Edition>().is_ok() => + { + if let Some(extra) = extra { + extra.error_invalid_codeblock_attr_with_help( + format!("unknown attribute `{x}`"), + |lint| { + lint.help(format!( + "there is an attribute with a similar name: `edition{}`", + &x[4..], + )); + }, + ); + } + } + LangStringToken::LangToken(x) if allow_error_code_check && x.starts_with('E') && x.len() == 5 => { if x[1..].parse::<u32>().is_ok() { @@ -1337,8 +1343,13 @@ impl LangString { } { if let Some(extra) = extra { extra.error_invalid_codeblock_attr_with_help( - format!("unknown attribute `{x}`. Did you mean `{flag}`?"), - help, + format!("unknown attribute `{x}`"), + |lint| { + lint.help(format!( + "there is an attribute with a similar name: `{flag}`" + )) + .help(help); + }, ); } } @@ -1370,7 +1381,7 @@ impl LangString { }; if custom_code_classes_in_docs { - call(&mut TagIterator::new(string, extra).into_iter()) + call(&mut TagIterator::new(string, extra)) } else { call(&mut tokens(string)) } @@ -2000,6 +2011,7 @@ fn init_id_map() -> FxHashMap<Cow<'static, str>, usize> { map.insert("themeStyle".into(), 1); map.insert("settings-menu".into(), 1); map.insert("help-button".into(), 1); + map.insert("sidebar-button".into(), 1); map.insert("main-content".into(), 1); map.insert("toggle-all-docs".into(), 1); map.insert("all-types".into(), 1); diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index 50777134d..f0199703c 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -176,9 +176,9 @@ impl<'tcx> Context<'tcx> { let mut render_redirect_pages = self.render_redirect_pages; // If the item is stripped but inlined, links won't point to the item so no need to generate // a file for it. - if it.is_stripped() && - let Some(def_id) = it.def_id() && - def_id.is_local() + if it.is_stripped() + && let Some(def_id) = it.def_id() + && def_id.is_local() { if self.is_inside_inlined_module || self.shared.cache.inlined_items.contains(&def_id) { // For now we're forced to generate a redirect page for stripped items until @@ -371,7 +371,9 @@ impl<'tcx> Context<'tcx> { path = href.into_inner().to_string_lossy().into_owned(); - if let Some(c) = path.as_bytes().last() && *c != b'/' { + if let Some(c) = path.as_bytes().last() + && *c != b'/' + { path.push('/'); } @@ -741,9 +743,10 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { shared.fs.write(scrape_examples_help_file, v)?; } - if let Some(ref redirections) = shared.redirections && !redirections.borrow().is_empty() { - let redirect_map_path = - self.dst.join(crate_name.as_str()).join("redirect-map.json"); + if let Some(ref redirections) = shared.redirections + && !redirections.borrow().is_empty() + { + let redirect_map_path = self.dst.join(crate_name.as_str()).join("redirect-map.json"); let paths = serde_json::to_string(&*redirections.borrow()).unwrap(); shared.ensure_dir(&self.dst.join(crate_name.as_str()))?; shared.fs.write(redirect_map_path, paths)?; @@ -790,7 +793,9 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> { } } if !self.is_inside_inlined_module { - if let Some(def_id) = item.def_id() && self.cache().inlined_items.contains(&def_id) { + if let Some(def_id) = item.def_id() + && self.cache().inlined_items.contains(&def_id) + { self.is_inside_inlined_module = true; } } else if !self.cache().document_hidden && item.is_doc_hidden() { diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index c52fa01bd..34350c2ed 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -45,7 +45,6 @@ use std::iter::Peekable; use std::path::PathBuf; use std::rc::Rc; use std::str; -use std::string::ToString; use askama::Template; use rustc_attr::{ConstStability, DeprecatedSince, Deprecation, StabilityLevel, StableSince}; @@ -66,7 +65,7 @@ use crate::clean::{self, ItemId, RenderedLink, SelfTy}; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::formats::{AssocItemRender, Impl, RenderMode}; +use crate::formats::Impl; use crate::html::escape::Escape; use crate::html::format::{ display_fn, href, join_with_double_colon, print_abi_with_space, print_constness_with_space, @@ -89,6 +88,21 @@ pub(crate) fn ensure_trailing_slash(v: &str) -> impl fmt::Display + '_ { }) } +/// Specifies whether rendering directly implemented trait items or ones from a certain Deref +/// impl. +pub(crate) enum AssocItemRender<'a> { + All, + DerefFor { trait_: &'a clean::Path, type_: &'a clean::Type, deref_mut_: bool }, +} + +/// For different handling of associated items from the Deref target of a type rather than the type +/// itself. +#[derive(Copy, Clone, PartialEq)] +pub(crate) enum RenderMode { + Normal, + ForDeref { mut_: bool }, +} + // Helper structs for rendering items/sidebars and carrying along contextual // information @@ -113,6 +127,7 @@ pub(crate) struct IndexItem { pub(crate) struct RenderType { id: Option<RenderTypeId>, generics: Option<Vec<RenderType>>, + bindings: Option<Vec<(RenderTypeId, Vec<RenderType>)>>, } impl Serialize for RenderType { @@ -129,10 +144,15 @@ impl Serialize for RenderType { Some(RenderTypeId::Index(idx)) => *idx, _ => panic!("must convert render types to indexes before serializing"), }; - if let Some(generics) = &self.generics { + if self.generics.is_some() || self.bindings.is_some() { let mut seq = serializer.serialize_seq(None)?; seq.serialize_element(&id)?; - seq.serialize_element(generics)?; + seq.serialize_element(self.generics.as_ref().map(Vec::as_slice).unwrap_or_default())?; + if self.bindings.is_some() { + seq.serialize_element( + self.bindings.as_ref().map(Vec::as_slice).unwrap_or_default(), + )?; + } seq.end() } else { id.serialize(serializer) @@ -140,13 +160,31 @@ impl Serialize for RenderType { } } -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub(crate) enum RenderTypeId { DefId(DefId), Primitive(clean::PrimitiveType), + AssociatedType(Symbol), Index(isize), } +impl Serialize for RenderTypeId { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let id = match &self { + // 0 is a sentinel, everything else is one-indexed + // concrete type + RenderTypeId::Index(idx) if *idx >= 0 => idx + 1, + // generic type parameter + RenderTypeId::Index(idx) => *idx, + _ => panic!("must convert render types to indexes before serializing"), + }; + id.serialize(serializer) + } +} + /// Full type of functions/methods in the search index. #[derive(Debug)] pub(crate) struct IndexItemFunctionType { @@ -171,16 +209,23 @@ impl Serialize for IndexItemFunctionType { } else { let mut seq = serializer.serialize_seq(None)?; match &self.inputs[..] { - [one] if one.generics.is_none() => seq.serialize_element(one)?, + [one] if one.generics.is_none() && one.bindings.is_none() => { + seq.serialize_element(one)? + } _ => seq.serialize_element(&self.inputs)?, } match &self.output[..] { [] if self.where_clause.is_empty() => {} - [one] if one.generics.is_none() => seq.serialize_element(one)?, + [one] if one.generics.is_none() && one.bindings.is_none() => { + seq.serialize_element(one)? + } _ => seq.serialize_element(&self.output)?, } for constraint in &self.where_clause { - if let [one] = &constraint[..] && one.generics.is_none() { + if let [one] = &constraint[..] + && one.generics.is_none() + && one.bindings.is_none() + { seq.serialize_element(one)?; } else { seq.serialize_element(constraint)?; @@ -627,7 +672,7 @@ fn short_item_info( format!("Deprecating in {version}") } } - DeprecatedSince::Future => String::from("Deprecating in a future Rust version"), + DeprecatedSince::Future => String::from("Deprecating in a future version"), DeprecatedSince::NonStandard(since) => { format!("Deprecated since {}", Escape(since.as_str())) } @@ -915,7 +960,9 @@ fn render_stability_since_raw_with_extra( containing_const_ver: Option<StableSince>, extra_class: &str, ) -> bool { - let stable_version = if ver != containing_ver && let Some(ver) = &ver { + let stable_version = if ver != containing_ver + && let Some(ver) = &ver + { since_to_string(ver) } else { None @@ -1097,7 +1144,7 @@ impl<'a> AssocItemLink<'a> { fn write_impl_section_heading(mut w: impl fmt::Write, title: &str, id: &str) { write!( w, - "<h2 id=\"{id}\" class=\"small-section-header\">\ + "<h2 id=\"{id}\" class=\"section-header\">\ {title}\ <a href=\"#{id}\" class=\"anchor\">§</a>\ </h2>" @@ -1348,8 +1395,7 @@ pub(crate) fn notable_traits_button(ty: &clean::Type, cx: &mut Context<'_>) -> O if let Some(trait_) = &impl_.trait_ { let trait_did = trait_.def_id(); - if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) - { + if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) { has_notable_trait = true; } } @@ -1384,7 +1430,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { if let Some(trait_) = &impl_.trait_ { let trait_did = trait_.def_id(); - if cx.cache().traits.get(&trait_did).map_or(false, |t| t.is_notable_trait(cx.tcx())) { + if cx.cache().traits.get(&trait_did).is_some_and(|t| t.is_notable_trait(cx.tcx())) { if out.is_empty() { write!( &mut out, @@ -1394,15 +1440,10 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { ); } - //use the "where" class here to make it small - write!( - &mut out, - "<span class=\"where fmt-newline\">{}</span>", - impl_.print(false, cx) - ); + write!(&mut out, "<div class=\"where\">{}</div>", impl_.print(false, cx)); for it in &impl_.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind { - out.push_str("<span class=\"where fmt-newline\"> "); + out.push_str("<div class=\"where\"> "); let empty_set = FxHashSet::default(); let src_link = AssocItemLink::GotoSource(trait_did.into(), &empty_set); assoc_type( @@ -1415,7 +1456,7 @@ fn notable_traits_decl(ty: &clean::Type, cx: &Context<'_>) -> (String, String) { 0, cx, ); - out.push_str(";</span>"); + out.push_str(";</div>"); } } } @@ -1901,7 +1942,7 @@ pub(crate) fn render_impl_summary( if show_def_docs { for it in &inner_impl.items { if let clean::AssocTypeItem(ref tydef, ref _bounds) = *it.kind { - w.write_str("<span class=\"where fmt-newline\"> "); + w.write_str("<div class=\"where\"> "); assoc_type( w, it, @@ -1912,7 +1953,7 @@ pub(crate) fn render_impl_summary( 0, cx, ); - w.write_str(";</span>"); + w.write_str(";</div>"); } } } diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index d226701ba..5ca623f01 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -1,5 +1,3 @@ -use clean::AttributesExt; - use rustc_data_structures::captures::Captures; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; @@ -22,12 +20,13 @@ use super::{ item_ty_to_section, notable_traits_button, notable_traits_json, render_all_impls, render_assoc_item, render_assoc_items, render_attributes_in_code, render_attributes_in_pre, render_impl, render_rightside, render_stability_since_raw, - render_stability_since_raw_with_extra, AssocItemLink, Context, ImplRenderingParameters, + render_stability_since_raw_with_extra, AssocItemLink, AssocItemRender, Context, + ImplRenderingParameters, RenderMode, }; use crate::clean; use crate::config::ModuleSorting; use crate::formats::item_type::ItemType; -use crate::formats::{AssocItemRender, Impl, RenderMode}; +use crate::formats::Impl; use crate::html::escape::Escape; use crate::html::format::{ display_fn, join_with_double_colon, print_abi_with_space, print_constness_with_space, @@ -369,8 +368,8 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: if let (Some(a), Some(b)) = (s1, s2) { match (a.is_stable(), b.is_stable()) { (true, true) | (false, false) => {} - (false, true) => return Ordering::Less, - (true, false) => return Ordering::Greater, + (false, true) => return Ordering::Greater, + (true, false) => return Ordering::Less, } } let lhs = i1.name.unwrap_or(kw::Empty); @@ -429,7 +428,7 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: last_section = Some(my_section); write!( w, - "<h2 id=\"{id}\" class=\"small-section-header\">\ + "<h2 id=\"{id}\" class=\"section-header\">\ <a href=\"#{id}\">{name}</a>\ </h2>{ITEM_TABLE_OPEN}", id = cx.derive_id(my_section.id()), @@ -464,16 +463,9 @@ fn item_module(w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, items: clean::ImportItem(ref import) => { let stab_tags = if let Some(import_def_id) = import.source.did { - let ast_attrs = tcx.get_attrs_unchecked(import_def_id); - let import_attrs = Box::new(clean::Attributes::from_ast(ast_attrs)); - // Just need an item with the correct def_id and attrs - let import_item = clean::Item { - item_id: import_def_id.into(), - attrs: import_attrs, - cfg: ast_attrs.cfg(tcx, &cx.cache().hidden_cfg), - ..myitem.clone() - }; + let import_item = + clean::Item { item_id: import_def_id.into(), ..myitem.clone() }; let stab_tags = Some(extra_info_tags(&import_item, item, tcx).to_string()); stab_tags @@ -596,8 +588,10 @@ fn extra_info_tags<'a, 'tcx: 'a>( // The "rustc_private" crates are permanently unstable so it makes no sense // to render "unstable" everywhere. - if item.stability(tcx).as_ref().map(|s| s.is_unstable() && s.feature != sym::rustc_private) - == Some(true) + if item + .stability(tcx) + .as_ref() + .is_some_and(|s| s.is_unstable() && s.feature != sym::rustc_private) { write!(f, "{}", tag_html("unstable", "", "Experimental"))?; } @@ -824,7 +818,7 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: fn write_small_section_header(w: &mut Buffer, id: &str, title: &str, extra_content: &str) { write!( w, - "<h2 id=\"{0}\" class=\"small-section-header\">\ + "<h2 id=\"{0}\" class=\"section-header\">\ {1}<a href=\"#{0}\" class=\"anchor\">§</a>\ </h2>{2}", id, title, extra_content @@ -974,8 +968,9 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: // if any Types with the same name but different DefId have been found. let mut implementor_dups: FxHashMap<Symbol, (DefId, bool)> = FxHashMap::default(); for implementor in implementors { - if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cache) && - !did.is_local() { + if let Some(did) = implementor.inner_impl().for_.without_borrowed_ref().def_id(cache) + && !did.is_local() + { extern_crates.insert(did.krate); } match implementor.inner_impl().for_.without_borrowed_ref() { @@ -1152,9 +1147,10 @@ fn item_trait(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean: .take(cx.current.len()) .chain(std::iter::once("trait.impl")) .collect(); - if let Some(did) = it.item_id.as_def_id() && - let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) } && - let Some(fqp) = cache.exact_paths.get(&did).or_else(get_extern) { + if let Some(did) = it.item_id.as_def_id() + && let get_extern = { || cache.external_paths.get(&did).map(|s| &s.0) } + && let Some(fqp) = cache.exact_paths.get(&did).or_else(get_extern) + { js_src_path.extend(fqp[..fqp.len() - 1].iter().copied()); js_src_path.push_fmt(format_args!("{}.{}.js", it.type_(), fqp.last().unwrap())); } else { @@ -1255,7 +1251,7 @@ fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &c if let Some(inner_type) = &t.inner_type { write!( w, - "<h2 id=\"aliased-type\" class=\"small-section-header\">\ + "<h2 id=\"aliased-type\" class=\"section-header\">\ Aliased Type<a href=\"#aliased-type\" class=\"anchor\">§</a></h2>" ); @@ -1499,10 +1495,12 @@ fn print_tuple_struct_fields<'a, 'cx: 'a>( s: &'a [clean::Item], ) -> impl fmt::Display + 'a + Captures<'cx> { display_fn(|f| { - if s.iter() - .all(|field| matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..)))) + if !s.is_empty() + && s.iter().all(|field| { + matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) + }) { - return f.write_str("/* private fields */"); + return f.write_str("<span class=\"comment\">/* private fields */</span>"); } for (i, ty) in s.iter().enumerate() { @@ -1564,8 +1562,8 @@ fn should_show_enum_discriminant( ) -> bool { let mut has_variants_with_value = false; for variant in variants { - if let clean::VariantItem(ref var) = *variant.kind && - matches!(var.kind, clean::VariantKind::CLike) + if let clean::VariantItem(ref var) = *variant.kind + && matches!(var.kind, clean::VariantKind::CLike) { has_variants_with_value |= var.discriminant.is_some(); } else { @@ -1659,7 +1657,7 @@ fn render_enum_fields( } if variants_stripped && !is_non_exhaustive { - w.write_str(" // some variants omitted\n"); + w.write_str(" <span class=\"comment\">// some variants omitted</span>\n"); } if toggle { toggle_close(&mut w); @@ -1678,7 +1676,7 @@ fn item_variants( let tcx = cx.tcx(); write!( w, - "<h2 id=\"variants\" class=\"variants small-section-header\">\ + "<h2 id=\"variants\" class=\"variants section-header\">\ Variants{}<a href=\"#variants\" class=\"anchor\">§</a>\ </h2>\ {}\ @@ -1706,8 +1704,8 @@ fn item_variants( " rightside", ); w.write_str("<h3 class=\"code-header\">"); - if let clean::VariantItem(ref var) = *variant.kind && - let clean::VariantKind::CLike = var.kind + if let clean::VariantItem(ref var) = *variant.kind + && let clean::VariantKind::CLike = var.kind { display_c_like_variant( w, @@ -1730,7 +1728,14 @@ fn item_variants( w.write_str("</h3></section>"); let heading_and_fields = match &variant_data.kind { - clean::VariantKind::Struct(s) => Some(("Fields", &s.fields)), + clean::VariantKind::Struct(s) => { + // If there is no field to display, no need to add the heading. + if s.fields.iter().any(|f| !f.is_doc_hidden()) { + Some(("Fields", &s.fields)) + } else { + None + } + } clean::VariantKind::Tuple(fields) => { // Documentation on tuple variant fields is rare, so to reduce noise we only emit // the section if at least one field is documented. @@ -1765,7 +1770,7 @@ fn item_variants( write!( w, "<div class=\"sub-variant-field\">\ - <span id=\"{id}\" class=\"small-section-header\">\ + <span id=\"{id}\" class=\"section-header\">\ <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{f}: {t}</code>\ </span>", @@ -1804,7 +1809,8 @@ fn item_proc_macro( let name = it.name.expect("proc-macros always have names"); match m.kind { MacroKind::Bang => { - write!(buffer, "{name}!() {{ /* proc-macro */ }}").unwrap(); + write!(buffer, "{name}!() {{ <span class=\"comment\">/* proc-macro */</span> }}") + .unwrap(); } MacroKind::Attr => { write!(buffer, "#[{name}]").unwrap(); @@ -1812,7 +1818,12 @@ fn item_proc_macro( MacroKind::Derive => { write!(buffer, "#[derive({name})]").unwrap(); if !m.helpers.is_empty() { - buffer.write_str("\n{\n // Attributes available to this derive:\n").unwrap(); + buffer + .write_str( + "\n{\n \ + <span class=\"comment\">// Attributes available to this derive:</span>\n", + ) + .unwrap(); for attr in &m.helpers { writeln!(buffer, " #[{attr}]").unwrap(); } @@ -1922,7 +1933,7 @@ fn item_fields( if fields.peek().is_some() { write!( w, - "<h2 id=\"fields\" class=\"fields small-section-header\">\ + "<h2 id=\"fields\" class=\"fields section-header\">\ {}{}<a href=\"#fields\" class=\"anchor\">§</a>\ </h2>\ {}", @@ -1936,7 +1947,7 @@ fn item_fields( let id = cx.derive_id(format!("{typ}.{field_name}", typ = ItemType::StructField)); write!( w, - "<span id=\"{id}\" class=\"{item_type} small-section-header\">\ + "<span id=\"{id}\" class=\"{item_type} section-header\">\ <a href=\"#{id}\" class=\"anchor field\">§</a>\ <code>{field_name}: {ty}</code>\ </span>", @@ -2174,7 +2185,7 @@ fn render_union<'a, 'cx: 'a>( } if it.has_stripped_entries().unwrap() { - write!(f, " /* private fields */\n")?; + write!(f, " <span class=\"comment\">/* private fields */</span>\n")?; } if toggle { toggle_close(&mut f); @@ -2260,11 +2271,11 @@ fn render_struct_fields( if has_visible_fields { if has_stripped_entries { - write!(w, "\n{tab} /* private fields */"); + write!(w, "\n{tab} <span class=\"comment\">/* private fields */</span>"); } write!(w, "\n{tab}"); } else if has_stripped_entries { - write!(w, " /* private fields */ "); + write!(w, " <span class=\"comment\">/* private fields */</span> "); } if toggle { toggle_close(&mut w); @@ -2273,10 +2284,12 @@ fn render_struct_fields( } Some(CtorKind::Fn) => { w.write_str("("); - if fields.iter().all(|field| { - matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) - }) { - write!(w, "/* private fields */"); + if !fields.is_empty() + && fields.iter().all(|field| { + matches!(*field.kind, clean::StrippedItem(box clean::StructFieldItem(..))) + }) + { + write!(w, "<span class=\"comment\">/* private fields */</span>"); } else { for (i, field) in fields.iter().enumerate() { if i > 0 { diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index af1dab594..a1029320d 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -3,8 +3,10 @@ use std::collections::BTreeMap; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_middle::ty::TyCtxt; +use rustc_span::def_id::DefId; use rustc_span::symbol::Symbol; use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer}; +use thin_vec::ThinVec; use crate::clean; use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate}; @@ -22,6 +24,7 @@ pub(crate) fn build_index<'tcx>( ) -> String { let mut itemid_to_pathid = FxHashMap::default(); let mut primitives = FxHashMap::default(); + let mut associated_types = FxHashMap::default(); let mut crate_paths = vec![]; // Attach all orphan items to the type's definition if the type @@ -38,7 +41,13 @@ pub(crate) fn build_index<'tcx>( parent: Some(parent), parent_idx: None, impl_id, - search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), + search_type: get_function_type_for_search( + item, + tcx, + impl_generics.as_ref(), + Some(parent), + cache, + ), aliases: item.attrs.get_doc_aliases(), deprecation: item.deprecation(tcx), }); @@ -76,83 +85,139 @@ pub(crate) fn build_index<'tcx>( let mut search_index = std::mem::replace(&mut cache.search_index, Vec::new()); for item in search_index.iter_mut() { fn insert_into_map<F: std::hash::Hash + Eq>( - ty: &mut RenderType, map: &mut FxHashMap<F, isize>, itemid: F, lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, item_type: ItemType, path: &[Symbol], - ) { + ) -> RenderTypeId { match map.entry(itemid) { - Entry::Occupied(entry) => ty.id = Some(RenderTypeId::Index(*entry.get())), + Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()), Entry::Vacant(entry) => { let pathid = *lastpathid; entry.insert(pathid); *lastpathid += 1; crate_paths.push((item_type, path.to_vec())); - ty.id = Some(RenderTypeId::Index(pathid)); + RenderTypeId::Index(pathid) } } } - fn convert_render_type( - ty: &mut RenderType, + fn convert_render_type_id( + id: RenderTypeId, cache: &mut Cache, itemid_to_pathid: &mut FxHashMap<ItemId, isize>, primitives: &mut FxHashMap<Symbol, isize>, + associated_types: &mut FxHashMap<Symbol, isize>, lastpathid: &mut isize, crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, - ) { - if let Some(generics) = &mut ty.generics { - for item in generics { - convert_render_type( - item, - cache, - itemid_to_pathid, - primitives, - lastpathid, - crate_paths, - ); - } - } + ) -> Option<RenderTypeId> { let Cache { ref paths, ref external_paths, .. } = *cache; - let Some(id) = ty.id.clone() else { - assert!(ty.generics.is_some()); - return; - }; match id { RenderTypeId::DefId(defid) => { if let Some(&(ref fqp, item_type)) = paths.get(&defid).or_else(|| external_paths.get(&defid)) { - insert_into_map( - ty, + Some(insert_into_map( itemid_to_pathid, ItemId::DefId(defid), lastpathid, crate_paths, item_type, fqp, - ); + )) } else { - ty.id = None; + None } } RenderTypeId::Primitive(primitive) => { let sym = primitive.as_sym(); - insert_into_map( - ty, + Some(insert_into_map( primitives, sym, lastpathid, crate_paths, ItemType::Primitive, &[sym], + )) + } + RenderTypeId::Index(_) => Some(id), + RenderTypeId::AssociatedType(sym) => Some(insert_into_map( + associated_types, + sym, + lastpathid, + crate_paths, + ItemType::AssocType, + &[sym], + )), + } + } + + fn convert_render_type( + ty: &mut RenderType, + cache: &mut Cache, + itemid_to_pathid: &mut FxHashMap<ItemId, isize>, + primitives: &mut FxHashMap<Symbol, isize>, + associated_types: &mut FxHashMap<Symbol, isize>, + lastpathid: &mut isize, + crate_paths: &mut Vec<(ItemType, Vec<Symbol>)>, + ) { + if let Some(generics) = &mut ty.generics { + for item in generics { + convert_render_type( + item, + cache, + itemid_to_pathid, + primitives, + associated_types, + lastpathid, + crate_paths, ); } - RenderTypeId::Index(_) => {} } + if let Some(bindings) = &mut ty.bindings { + bindings.retain_mut(|(associated_type, constraints)| { + let converted_associated_type = convert_render_type_id( + *associated_type, + cache, + itemid_to_pathid, + primitives, + associated_types, + lastpathid, + crate_paths, + ); + let Some(converted_associated_type) = converted_associated_type else { + return false; + }; + *associated_type = converted_associated_type; + for constraint in constraints { + convert_render_type( + constraint, + cache, + itemid_to_pathid, + primitives, + associated_types, + lastpathid, + crate_paths, + ); + } + true + }); + } + let Some(id) = ty.id.clone() else { + assert!(ty.generics.is_some()); + return; + }; + ty.id = convert_render_type_id( + id, + cache, + itemid_to_pathid, + primitives, + associated_types, + lastpathid, + crate_paths, + ); } if let Some(search_type) = &mut item.search_type { for item in &mut search_type.inputs { @@ -161,6 +226,7 @@ pub(crate) fn build_index<'tcx>( cache, &mut itemid_to_pathid, &mut primitives, + &mut associated_types, &mut lastpathid, &mut crate_paths, ); @@ -171,6 +237,7 @@ pub(crate) fn build_index<'tcx>( cache, &mut itemid_to_pathid, &mut primitives, + &mut associated_types, &mut lastpathid, &mut crate_paths, ); @@ -182,6 +249,7 @@ pub(crate) fn build_index<'tcx>( cache, &mut itemid_to_pathid, &mut primitives, + &mut associated_types, &mut lastpathid, &mut crate_paths, ); @@ -228,10 +296,11 @@ pub(crate) fn build_index<'tcx>( let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default(); for &item in &crate_items { - if item.impl_id.is_some() && let Some(parent_idx) = item.parent_idx { - let count = associated_item_duplicates - .entry((parent_idx, item.ty, item.name)) - .or_insert(0); + if item.impl_id.is_some() + && let Some(parent_idx) = item.parent_idx + { + let count = + associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0); *count += 1; } } @@ -419,7 +488,7 @@ pub(crate) fn build_index<'tcx>( // Collect the index into a string format!( - r#""{}":{}"#, + r#"["{}",{}]"#, krate.name(tcx), serde_json::to_string(&CrateData { doc: crate_doc, @@ -441,12 +510,39 @@ pub(crate) fn get_function_type_for_search<'tcx>( item: &clean::Item, tcx: TyCtxt<'tcx>, impl_generics: Option<&(clean::Type, clean::Generics)>, + parent: Option<DefId>, cache: &Cache, ) -> Option<IndexItemFunctionType> { + let mut trait_info = None; + let impl_or_trait_generics = impl_generics.or_else(|| { + if let Some(def_id) = parent + && let Some(trait_) = cache.traits.get(&def_id) + && let Some((path, _)) = + cache.paths.get(&def_id).or_else(|| cache.external_paths.get(&def_id)) + { + let path = clean::Path { + res: rustc_hir::def::Res::Def(rustc_hir::def::DefKind::Trait, def_id), + segments: path + .iter() + .map(|name| clean::PathSegment { + name: *name, + args: clean::GenericArgs::AngleBracketed { + args: Vec::new().into_boxed_slice(), + bindings: ThinVec::new(), + }, + }) + .collect(), + }; + trait_info = Some((clean::Type::Path { path }, trait_.generics.clone())); + Some(trait_info.as_ref().unwrap()) + } else { + None + } + }); let (mut inputs, mut output, where_clause) = match *item.kind { - clean::FunctionItem(ref f) => get_fn_inputs_and_outputs(f, tcx, impl_generics, cache), - clean::MethodItem(ref m, _) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), - clean::TyMethodItem(ref m) => get_fn_inputs_and_outputs(m, tcx, impl_generics, cache), + clean::FunctionItem(ref f) | clean::MethodItem(ref f, _) | clean::TyMethodItem(ref f) => { + get_fn_inputs_and_outputs(f, tcx, impl_or_trait_generics, cache) + } _ => return None, }; @@ -456,14 +552,23 @@ pub(crate) fn get_function_type_for_search<'tcx>( Some(IndexItemFunctionType { inputs, output, where_clause }) } -fn get_index_type(clean_type: &clean::Type, generics: Vec<RenderType>) -> RenderType { +fn get_index_type( + clean_type: &clean::Type, + generics: Vec<RenderType>, + rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, +) -> RenderType { RenderType { - id: get_index_type_id(clean_type), + id: get_index_type_id(clean_type, rgen), generics: if generics.is_empty() { None } else { Some(generics) }, + bindings: None, } } -fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> { +fn get_index_type_id( + clean_type: &clean::Type, + rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, +) -> Option<RenderTypeId> { + use rustc_hir::def::{DefKind, Res}; match *clean_type { clean::Type::Path { ref path, .. } => Some(RenderTypeId::DefId(path.def_id())), clean::DynTrait(ref bounds, _) => { @@ -471,18 +576,27 @@ fn get_index_type_id(clean_type: &clean::Type) -> Option<RenderTypeId> { } clean::Primitive(p) => Some(RenderTypeId::Primitive(p)), clean::BorrowedRef { ref type_, .. } | clean::RawPointer(_, ref type_) => { - get_index_type_id(type_) + get_index_type_id(type_, rgen) } // The type parameters are converted to generics in `simplify_fn_type` clean::Slice(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Slice)), clean::Array(_, _) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Array)), clean::Tuple(_) => Some(RenderTypeId::Primitive(clean::PrimitiveType::Tuple)), + clean::QPath(ref data) => { + if data.self_type.is_self_type() + && let Some(clean::Path { res: Res::Def(DefKind::Trait, trait_), .. }) = data.trait_ + { + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + let (idx, _) = rgen + .entry(SimplifiedParam::AssociatedType(trait_, data.assoc.name)) + .or_insert_with(|| (idx, Vec::new())); + Some(RenderTypeId::Index(*idx)) + } else { + None + } + } // Not supported yet - clean::BareFunction(_) - | clean::Generic(_) - | clean::ImplTrait(_) - | clean::QPath { .. } - | clean::Infer => None, + clean::BareFunction(_) | clean::Generic(_) | clean::ImplTrait(_) | clean::Infer => None, } } @@ -492,6 +606,9 @@ enum SimplifiedParam { Symbol(Symbol), // every argument-position impl trait is its own type parameter Anonymous(isize), + // in a trait definition, the associated types are all bound to + // their own type parameter + AssociatedType(DefId, Symbol), } /// The point of this function is to lower generics and types into the simplified form that the @@ -522,10 +639,17 @@ fn simplify_fn_type<'tcx, 'a>( } // First, check if it's "Self". + let mut is_self = false; let mut arg = if let Some(self_) = self_ { match &*arg { - Type::BorrowedRef { type_, .. } if type_.is_self_type() => self_, - type_ if type_.is_self_type() => self_, + Type::BorrowedRef { type_, .. } if type_.is_self_type() => { + is_self = true; + self_ + } + type_ if type_.is_self_type() => { + is_self = true; + self_ + } arg => arg, } } else { @@ -584,11 +708,19 @@ fn simplify_fn_type<'tcx, 'a>( } } if let Some((idx, _)) = rgen.get(&SimplifiedParam::Symbol(arg_s)) { - res.push(RenderType { id: Some(RenderTypeId::Index(*idx)), generics: None }); + res.push(RenderType { + id: Some(RenderTypeId::Index(*idx)), + generics: None, + bindings: None, + }); } else { let idx = -isize::try_from(rgen.len() + 1).unwrap(); rgen.insert(SimplifiedParam::Symbol(arg_s), (idx, type_bounds)); - res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); + res.push(RenderType { + id: Some(RenderTypeId::Index(idx)), + generics: None, + bindings: None, + }); } } else if let Type::ImplTrait(ref bounds) = *arg { let mut type_bounds = Vec::new(); @@ -610,12 +742,16 @@ fn simplify_fn_type<'tcx, 'a>( } if is_return && !type_bounds.is_empty() { // In parameter position, `impl Trait` is a unique thing. - res.push(RenderType { id: None, generics: Some(type_bounds) }); + res.push(RenderType { id: None, generics: Some(type_bounds), bindings: None }); } else { // In parameter position, `impl Trait` is the same as an unnamed generic parameter. let idx = -isize::try_from(rgen.len() + 1).unwrap(); rgen.insert(SimplifiedParam::Anonymous(idx), (idx, type_bounds)); - res.push(RenderType { id: Some(RenderTypeId::Index(idx)), generics: None }); + res.push(RenderType { + id: Some(RenderTypeId::Index(idx)), + generics: None, + bindings: None, + }); } } else if let Type::Slice(ref ty) = *arg { let mut ty_generics = Vec::new(); @@ -630,7 +766,7 @@ fn simplify_fn_type<'tcx, 'a>( is_return, cache, ); - res.push(get_index_type(arg, ty_generics)); + res.push(get_index_type(arg, ty_generics, rgen)); } else if let Type::Array(ref ty, _) = *arg { let mut ty_generics = Vec::new(); simplify_fn_type( @@ -644,7 +780,7 @@ fn simplify_fn_type<'tcx, 'a>( is_return, cache, ); - res.push(get_index_type(arg, ty_generics)); + res.push(get_index_type(arg, ty_generics, rgen)); } else if let Type::Tuple(ref tys) = *arg { let mut ty_generics = Vec::new(); for ty in tys { @@ -660,7 +796,7 @@ fn simplify_fn_type<'tcx, 'a>( cache, ); } - res.push(get_index_type(arg, ty_generics)); + res.push(get_index_type(arg, ty_generics, rgen)); } else { // This is not a type parameter. So for example if we have `T, U: Option<T>`, and we're // looking at `Option`, we enter this "else" condition, otherwise if it's `T`, we don't. @@ -668,12 +804,16 @@ fn simplify_fn_type<'tcx, 'a>( // So in here, we can add it directly and look for its own type parameters (so for `Option`, // we will look for them but not for `T`). let mut ty_generics = Vec::new(); - if let Some(arg_generics) = arg.generics() { - for gen in arg_generics.iter() { + let mut ty_bindings = Vec::new(); + if let Some(arg_generics) = arg.generic_args() { + for ty in arg_generics.into_iter().filter_map(|gen| match gen { + clean::GenericArg::Type(ty) => Some(ty), + _ => None, + }) { simplify_fn_type( self_, generics, - gen, + &ty, tcx, recurse + 1, &mut ty_generics, @@ -682,17 +822,180 @@ fn simplify_fn_type<'tcx, 'a>( cache, ); } + for binding in arg_generics.bindings() { + simplify_fn_binding( + self_, + generics, + &binding, + tcx, + recurse + 1, + &mut ty_bindings, + rgen, + is_return, + cache, + ); + } + } + // Every trait associated type on self gets assigned to a type parameter index + // this same one is used later for any appearances of these types + // + // for example, Iterator::next is: + // + // trait Iterator { + // fn next(&mut self) -> Option<Self::Item> + // } + // + // Self is technically just Iterator, but we want to pretend it's more like this: + // + // fn next<T>(self: Iterator<Item=T>) -> Option<T> + if is_self + && let Type::Path { path } = arg + && let def_id = path.def_id() + && let Some(trait_) = cache.traits.get(&def_id) + && trait_.items.iter().any(|at| at.is_ty_associated_type()) + { + for assoc_ty in &trait_.items { + if let clean::ItemKind::TyAssocTypeItem(_generics, bounds) = &*assoc_ty.kind + && let Some(name) = assoc_ty.name + { + let idx = -isize::try_from(rgen.len() + 1).unwrap(); + let (idx, stored_bounds) = rgen + .entry(SimplifiedParam::AssociatedType(def_id, name)) + .or_insert_with(|| (idx, Vec::new())); + let idx = *idx; + if stored_bounds.is_empty() { + // Can't just pass stored_bounds to simplify_fn_type, + // because it also accepts rgen as a parameter. + // Instead, have it fill in this local, then copy it into the map afterward. + let mut type_bounds = Vec::new(); + for bound in bounds { + if let Some(path) = bound.get_trait_path() { + let ty = Type::Path { path }; + simplify_fn_type( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut type_bounds, + rgen, + is_return, + cache, + ); + } + } + let stored_bounds = &mut rgen + .get_mut(&SimplifiedParam::AssociatedType(def_id, name)) + .unwrap() + .1; + if stored_bounds.is_empty() { + *stored_bounds = type_bounds; + } + } + ty_bindings.push(( + RenderTypeId::AssociatedType(name), + vec![RenderType { + id: Some(RenderTypeId::Index(idx)), + generics: None, + bindings: None, + }], + )) + } + } } - let id = get_index_type_id(&arg); + let id = get_index_type_id(&arg, rgen); if id.is_some() || !ty_generics.is_empty() { res.push(RenderType { id, + bindings: if ty_bindings.is_empty() { None } else { Some(ty_bindings) }, generics: if ty_generics.is_empty() { None } else { Some(ty_generics) }, }); } } } +fn simplify_fn_binding<'tcx, 'a>( + self_: Option<&'a Type>, + generics: &Generics, + binding: &'a clean::TypeBinding, + tcx: TyCtxt<'tcx>, + recurse: usize, + res: &mut Vec<(RenderTypeId, Vec<RenderType>)>, + rgen: &mut FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)>, + is_return: bool, + cache: &Cache, +) { + let mut ty_binding_constraints = Vec::new(); + let ty_binding_assoc = RenderTypeId::AssociatedType(binding.assoc.name); + for gen in &binding.assoc.args { + match gen { + clean::GenericArg::Type(arg) => simplify_fn_type( + self_, + generics, + &arg, + tcx, + recurse + 1, + &mut ty_binding_constraints, + rgen, + is_return, + cache, + ), + clean::GenericArg::Lifetime(_) + | clean::GenericArg::Const(_) + | clean::GenericArg::Infer => {} + } + } + for binding in binding.assoc.args.bindings() { + simplify_fn_binding( + self_, + generics, + &binding, + tcx, + recurse + 1, + res, + rgen, + is_return, + cache, + ); + } + match &binding.kind { + clean::TypeBindingKind::Equality { term } => { + if let clean::Term::Type(arg) = &term { + simplify_fn_type( + self_, + generics, + arg, + tcx, + recurse + 1, + &mut ty_binding_constraints, + rgen, + is_return, + cache, + ); + } + } + clean::TypeBindingKind::Constraint { bounds } => { + for bound in &bounds[..] { + if let Some(path) = bound.get_trait_path() { + let ty = Type::Path { path }; + simplify_fn_type( + self_, + generics, + &ty, + tcx, + recurse + 1, + &mut ty_binding_constraints, + rgen, + is_return, + cache, + ); + } + } + } + } + res.push((ty_binding_assoc, ty_binding_constraints)); +} + /// Return the full list of types when bounds have been resolved. /// /// i.e. `fn foo<A: Display, B: Option<A>>(x: u32, y: B)` will return @@ -700,13 +1003,15 @@ fn simplify_fn_type<'tcx, 'a>( fn get_fn_inputs_and_outputs<'tcx>( func: &Function, tcx: TyCtxt<'tcx>, - impl_generics: Option<&(clean::Type, clean::Generics)>, + impl_or_trait_generics: Option<&(clean::Type, clean::Generics)>, cache: &Cache, ) -> (Vec<RenderType>, Vec<RenderType>, Vec<Vec<RenderType>>) { let decl = &func.decl; + let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); + let combined_generics; - let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_generics { + let (self_, generics) = if let Some((impl_self, impl_generics)) = impl_or_trait_generics { match (impl_generics.is_empty(), func.generics.is_empty()) { (true, _) => (Some(impl_self), &func.generics), (_, true) => (Some(impl_self), impl_generics), @@ -728,8 +1033,6 @@ fn get_fn_inputs_and_outputs<'tcx>( (None, &func.generics) }; - let mut rgen: FxHashMap<SimplifiedParam, (isize, Vec<RenderType>)> = Default::default(); - let mut arg_types = Vec::new(); for arg in decl.inputs.values.iter() { simplify_fn_type( diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs index ba4aaaff5..3d28937eb 100644 --- a/src/librustdoc/html/render/sidebar.rs +++ b/src/librustdoc/html/render/sidebar.rs @@ -435,9 +435,9 @@ fn sidebar_deref_methods<'a>( } // Recurse into any further impls that might exist for `target` - if let Some(target_did) = target.def_id(c) && - let Some(target_impls) = c.impls.get(&target_did) && - let Some(target_deref_impl) = target_impls.iter().find(|i| { + if let Some(target_did) = target.def_id(c) + && let Some(target_impls) = c.impls.get(&target_did) + && let Some(target_deref_impl) = target_impls.iter().find(|i| { i.inner_impl() .trait_ .as_ref() @@ -445,14 +445,7 @@ fn sidebar_deref_methods<'a>( .unwrap_or(false) }) { - sidebar_deref_methods( - cx, - out, - target_deref_impl, - target_impls, - derefs, - used_links, - ); + sidebar_deref_methods(cx, out, target_deref_impl, target_impls, derefs, used_links); } } } @@ -494,8 +487,13 @@ fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> { && it .name .or_else(|| { - if let clean::ImportItem(ref i) = *it.kind && - let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None } + if let clean::ImportItem(ref i) = *it.kind + && let clean::ImportKind::Simple(s) = i.kind + { + Some(s) + } else { + None + } }) .is_some() }) diff --git a/src/librustdoc/html/render/span_map.rs b/src/librustdoc/html/render/span_map.rs index 5f130f187..d1ece7337 100644 --- a/src/librustdoc/html/render/span_map.rs +++ b/src/librustdoc/html/render/span_map.rs @@ -94,7 +94,7 @@ impl<'tcx> SpanMapVisitor<'tcx> { /// Used to generate links on items' definition to go to their documentation page. pub(crate) fn extract_info_from_hir_id(&mut self, hir_id: HirId) { - if let Some(Node::Item(item)) = self.tcx.hir().find(hir_id) { + if let Some(Node::Item(item)) = self.tcx.opt_hir_node(hir_id) { if let Some(span) = self.tcx.def_ident_span(item.owner_id) { let cspan = clean::Span::new(span); // If the span isn't from the current crate, we ignore it. @@ -177,7 +177,7 @@ impl<'tcx> Visitor<'tcx> for SpanMapVisitor<'tcx> { if !span.overlaps(m.spans.inner_span) { // 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::Item(item)) = self.tcx.hir().find(id) { + if let Some(Node::Item(item)) = self.tcx.opt_hir_node(id) { self.matches.insert( item.ident.span, LinkFromSrc::Local(clean::Span::new(m.spans.inner_span)), diff --git a/src/librustdoc/html/render/type_layout.rs b/src/librustdoc/html/render/type_layout.rs index 377daaeb9..738ea0aee 100644 --- a/src/librustdoc/html/render/type_layout.rs +++ b/src/librustdoc/html/render/type_layout.rs @@ -42,37 +42,35 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>( let ty = tcx.type_of(ty_def_id).instantiate_identity(); let type_layout = tcx.layout_of(param_env.and(ty)); - let variants = - if let Ok(type_layout) = type_layout && - let Variants::Multiple { variants, tag, tag_encoding, .. } = - type_layout.layout.variants() && - !variants.is_empty() - { - let tag_size = - if let TagEncoding::Niche { .. } = tag_encoding { - 0 - } else if let Primitive::Int(i, _) = tag.primitive() { - i.size().bytes() - } else { - span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") - }; - variants - .iter_enumerated() - .map(|(variant_idx, variant_layout)| { - let Adt(adt, _) = type_layout.ty.kind() else { - span_bug!(tcx.def_span(ty_def_id), "not an adt") - }; - let name = adt.variant(variant_idx).name; - let is_unsized = variant_layout.abi.is_unsized(); - let is_uninhabited = variant_layout.abi.is_uninhabited(); - let size = variant_layout.size.bytes() - tag_size; - let type_layout_size = TypeLayoutSize { is_unsized, is_uninhabited, size }; - (name, type_layout_size) - }) - .collect() + let variants = if let Ok(type_layout) = type_layout + && let Variants::Multiple { variants, tag, tag_encoding, .. } = + type_layout.layout.variants() + && !variants.is_empty() + { + let tag_size = if let TagEncoding::Niche { .. } = tag_encoding { + 0 + } else if let Primitive::Int(i, _) = tag.primitive() { + i.size().bytes() } else { - Vec::new() + span_bug!(tcx.def_span(ty_def_id), "tag is neither niche nor int") }; + variants + .iter_enumerated() + .map(|(variant_idx, variant_layout)| { + let Adt(adt, _) = type_layout.ty.kind() else { + span_bug!(tcx.def_span(ty_def_id), "not an adt") + }; + let name = adt.variant(variant_idx).name; + let is_unsized = variant_layout.abi.is_unsized(); + let is_uninhabited = variant_layout.abi.is_uninhabited(); + let size = variant_layout.size.bytes() - tag_size; + let type_layout_size = TypeLayoutSize { is_unsized, is_uninhabited, size }; + (name, type_layout_size) + }) + .collect() + } else { + Vec::new() + }; let type_layout_size = tcx.layout_of(param_env.and(ty)).map(|layout| { let is_unsized = layout.abi.is_unsized(); @@ -81,6 +79,7 @@ pub(crate) fn document_type_layout<'a, 'cx: 'a>( TypeLayoutSize { is_unsized, is_uninhabited, size } }); - Ok(TypeLayout { variants, type_layout_size }.render_into(f).unwrap()) + TypeLayout { variants, type_layout_size }.render_into(f).unwrap(); + Ok(()) }) } diff --git a/src/librustdoc/html/render/write_shared.rs b/src/librustdoc/html/render/write_shared.rs index d2c7c578c..6408e97df 100644 --- a/src/librustdoc/html/render/write_shared.rs +++ b/src/librustdoc/html/render/write_shared.rs @@ -15,14 +15,14 @@ use rustc_span::Symbol; use serde::ser::SerializeSeq; use serde::{Serialize, Serializer}; -use super::{collect_paths_for_type, ensure_trailing_slash, Context}; +use super::{collect_paths_for_type, ensure_trailing_slash, Context, RenderMode}; use crate::clean::{Crate, Item, ItemId, ItemKind}; use crate::config::{EmitType, RenderOptions}; use crate::docfs::PathError; use crate::error::Error; use crate::formats::cache::Cache; use crate::formats::item_type::ItemType; -use crate::formats::{Impl, RenderMode}; +use crate::formats::Impl; use crate::html::format::Buffer; use crate::html::render::{AssocItemLink, ImplRenderingParameters}; use crate::html::{layout, static_files}; @@ -167,23 +167,24 @@ pub(super) fn write_shared( let mut krates = Vec::new(); if path.exists() { - let prefix = format!("\"{krate}\""); + let prefix = format!("[\"{krate}\""); for line in BufReader::new(File::open(path)?).lines() { let line = line?; - if !line.starts_with('"') { + if !line.starts_with("[\"") { continue; } if line.starts_with(&prefix) { continue; } - if line.ends_with(",\\") { + if line.ends_with("],\\") { ret.push(line[..line.len() - 2].to_string()); } else { // Ends with "\\" (it's the case for the last added crate line) ret.push(line[..line.len() - 1].to_string()); } krates.push( - line.split('"') + line[1..] // We skip the `[` parent at the beginning of the line. + .split('"') .find(|s| !s.is_empty()) .map(|s| s.to_owned()) .unwrap_or_else(String::new), @@ -285,7 +286,7 @@ pub(super) fn write_shared( let (mut all_sources, _krates) = try_err!(collect_json(&dst, krate.name(cx.tcx()).as_str()), &dst); all_sources.push(format!( - r#""{}":{}"#, + r#"["{}",{}]"#, &krate.name(cx.tcx()), hierarchy .to_json_string() @@ -296,9 +297,12 @@ pub(super) fn write_shared( .replace("\\\"", "\\\\\"") )); all_sources.sort(); - let mut v = String::from("var srcIndex = JSON.parse('{\\\n"); + // This needs to be `var`, not `const`. + // This variable needs declared in the current global scope so that if + // src-script.js loads first, it can pick it up. + let mut v = String::from("var srcIndex = new Map(JSON.parse('[\\\n"); v.push_str(&all_sources.join(",\\\n")); - v.push_str("\\\n}');\ncreateSrcSidebar();\n"); + v.push_str("\\\n]'));\ncreateSrcSidebar();\n"); Ok(v.into_bytes()) }; write_invocation_specific("src-files.js", &make_sources)?; @@ -316,13 +320,16 @@ pub(super) fn write_shared( // with rustdoc running in parallel. all_indexes.sort(); write_invocation_specific("search-index.js", &|| { - let mut v = String::from("var searchIndex = JSON.parse('{\\\n"); + // This needs to be `var`, not `const`. + // This variable needs declared in the current global scope so that if + // search.js loads first, it can pick it up. + let mut v = String::from("var searchIndex = new Map(JSON.parse('[\\\n"); v.push_str(&all_indexes.join(",\\\n")); v.push_str( r#"\ -}'); -if (typeof window !== 'undefined' && window.initSearch) {window.initSearch(searchIndex)}; -if (typeof exports !== 'undefined') {exports.searchIndex = searchIndex}; +]')); +if (typeof exports !== 'undefined') exports.searchIndex = searchIndex; +else if (window.initSearch) window.initSearch(searchIndex); "#, ); Ok(v.into_bytes()) diff --git a/src/librustdoc/html/sources.rs b/src/librustdoc/html/sources.rs index ce620c226..e160ec12f 100644 --- a/src/librustdoc/html/sources.rs +++ b/src/librustdoc/html/sources.rs @@ -90,7 +90,9 @@ impl LocalSourcesCollector<'_, '_> { ); let mut href = href.into_inner().to_string_lossy().into_owned(); - if let Some(c) = href.as_bytes().last() && *c != b'/' { + if let Some(c) = href.as_bytes().last() + && *c != b'/' + { href.push('/'); } let mut src_fname = p.file_name().expect("source has no filename").to_os_string(); @@ -212,7 +214,9 @@ impl SourceCollector<'_, '_> { let root_path = PathBuf::from("../../").join(root_path.into_inner()); let mut root_path = root_path.to_string_lossy(); - if let Some(c) = root_path.as_bytes().last() && *c != b'/' { + if let Some(c) = root_path.as_bytes().last() + && *c != b'/' + { root_path += "/"; } let mut cur = self.dst.join(cur.into_inner()); diff --git a/src/librustdoc/html/static/css/noscript.css b/src/librustdoc/html/static/css/noscript.css index fe0cf6dc8..390e81277 100644 --- a/src/librustdoc/html/static/css/noscript.css +++ b/src/librustdoc/html/static/css/noscript.css @@ -9,7 +9,7 @@ rules. margin-left: 0 !important; } -#copy-path { +#copy-path, #sidebar-button, .sidebar-resizer { /* It requires JS to work so no need to display it in this case. */ display: none; } @@ -132,6 +132,8 @@ nav.sub { --scrape-example-help-hover-color: #000; --scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1); --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); + --sidebar-resizer-hover: hsl(207, 90%, 66%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: light */ @@ -238,6 +240,8 @@ nav.sub { --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1); --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); + --sidebar-resizer-hover: hsl(207, 30%, 54%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: dark */ } diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 9efdcd601..c4e97ded1 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -9,6 +9,12 @@ :root { --nav-sub-mobile-padding: 8px; --search-typename-width: 6.75rem; + /* DEFAULT_SIDEBAR_WIDTH + see main.js for information on these values + and on the RUSTDOC_MOBILE_BREAKPOINT */ + --desktop-sidebar-width: 200px; + --src-sidebar-width: 300px; + --desktop-sidebar-z-index: 100; } /* See FiraSans-LICENSE.txt for the Fira Sans license. */ @@ -205,7 +211,7 @@ ul.all-items { #toggle-all-docs, a.anchor, -.small-section-header a, +.section-header a, #src-sidebar a, .rust a, .sidebar h2 a, @@ -383,13 +389,15 @@ img { .sidebar { font-size: 0.875rem; - flex: 0 0 200px; + flex: 0 0 var(--desktop-sidebar-width); + width: var(--desktop-sidebar-width); overflow-y: scroll; overscroll-behavior: contain; position: sticky; height: 100vh; top: 0; left: 0; + z-index: var(--desktop-sidebar-z-index); } .rustdoc.src .sidebar { @@ -398,7 +406,94 @@ img { overflow-x: hidden; /* The sidebar is by default hidden */ overflow-y: hidden; - z-index: 1; +} + +.hide-sidebar .sidebar, +.hide-sidebar .sidebar-resizer { + display: none; +} + +.sidebar-resizer { + touch-action: none; + width: 9px; + cursor: col-resize; + z-index: calc(var(--desktop-sidebar-z-index) + 1); + position: fixed; + height: 100%; + /* make sure there's a 1px gap between the scrollbar and resize handle */ + left: calc(var(--desktop-sidebar-width) + 1px); +} + +.rustdoc.src .sidebar-resizer { + /* when closed, place resizer glow on top of the normal src sidebar border (no need to worry + about sidebar) */ + left: 49px; +} + +.src-sidebar-expanded .rustdoc.src .sidebar-resizer { + /* for src sidebar, gap is already provided by 1px border on sidebar itself, so place resizer + to right of it */ + left: var(--src-sidebar-width); +} + +.sidebar-resizing { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.sidebar-resizing * { + cursor: col-resize !important; +} + +.sidebar-resizing .sidebar { + position: fixed; +} +.sidebar-resizing > body { + padding-left: var(--resizing-sidebar-width); +} + +.sidebar-resizer:hover, +.sidebar-resizer:active, +.sidebar-resizer:focus, +.sidebar-resizer.active { + width: 10px; + margin: 0; + /* when active or hovered, place resizer glow on top of the sidebar (right next to, or even + on top of, the scrollbar) */ + left: var(--desktop-sidebar-width); + border-left: solid 1px var(--sidebar-resizer-hover); +} + +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:hover, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:active, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer:focus, +.src-sidebar-expanded .rustdoc.src .sidebar-resizer.active { + /* when active or hovered, place resizer glow on top of the normal src sidebar border */ + left: calc(var(--src-sidebar-width) - 1px); +} + +@media (pointer: coarse) { + .sidebar-resizer { + /* too easy to hit the resizer while trying to hit the [-] toggle */ + display: none !important; + } +} + +.sidebar-resizer.active { + /* make the resize tool bigger when actually resizing, to avoid :hover styles on other stuff + while resizing */ + padding: 0 140px; + width: 2px; + margin-left: -140px; + border-left: none; +} +.sidebar-resizer.active:before { + border-left: solid 2px var(--sidebar-resizer-active); + display: block; + height: 100%; + content: ""; } .sidebar, .mobile-topbar, .sidebar-menu-toggle, @@ -416,7 +511,8 @@ img { .src-sidebar-expanded .src .sidebar { overflow-y: auto; - flex-basis: 300px; + flex-basis: var(--src-sidebar-width); + width: var(--src-sidebar-width); } .src-sidebar-expanded .src .sidebar > *:not(#src-sidebar-toggle) { @@ -477,6 +573,7 @@ ul.block, .block li { display: block; padding: 0.25rem; /* 4px */ margin-left: -0.25rem; + margin-right: 0.25rem; } .sidebar h2 { @@ -703,11 +800,8 @@ pre, .rustdoc.src .example-wrap { background: var(--table-alt-row-background-color); } -/* Shift "where ..." part of method or fn definition down a line */ -.method .where, -.fn .where, -.where.fmt-newline { - display: block; +/* "where ..." clauses with block display are also smaller */ +div.where { white-space: pre-wrap; font-size: 0.875rem; } @@ -742,13 +836,13 @@ nav.sub { margin: 0 0 15px 0; } -.small-section-header { +.section-header { /* fields use <span> tags, but should get their own lines */ display: block; position: relative; } -.small-section-header:hover > .anchor, .impl:hover > .anchor, +.section-header:hover > .anchor, .impl:hover > .anchor, .trait-impl:hover > .anchor, .variant:hover > .anchor { display: initial; } @@ -761,11 +855,11 @@ nav.sub { .anchor.field { left: -5px; } -.small-section-header > .anchor { +.section-header > .anchor { left: -15px; padding-right: 8px; } -h2.small-section-header > .anchor { +h2.section-header > .anchor { padding-right: 6px; } @@ -778,7 +872,7 @@ h2.small-section-header > .anchor { text-decoration: underline; } -.crate.block a.current { font-weight: 500; } +.crate.block li.current a { font-weight: 500; } /* In most contexts we use `overflow-wrap: anywhere` to ensure that we can wrap as much as needed on mobile (see @@ -965,7 +1059,7 @@ so that we can apply CSS-filters to change the arrow color in themes */ position: absolute; top: 100%; right: 0; - z-index: 2; + z-index: calc(var(--desktop-sidebar-z-index) + 1); margin-top: 7px; border-radius: 3px; border: 1px solid var(--border-color); @@ -1084,15 +1178,9 @@ so that we can apply CSS-filters to change the arrow color in themes */ } .item-info .stab { - /* This min-height is needed to unify the height of the stab elements because some of them - have emojis. - */ - min-height: 36px; - display: flex; + display: block; padding: 3px; margin-bottom: 5px; - align-items: center; - vertical-align: text-bottom; } .item-name .stab { margin-left: 0.3125em; @@ -1115,17 +1203,26 @@ so that we can apply CSS-filters to change the arrow color in themes */ color: var(--stab-code-color); } -.stab .emoji { +.stab .emoji, .item-info .stab::before { font-size: 1.25rem; +} +.stab .emoji { margin-right: 0.3rem; } +.item-info .stab::before { + /* ensure badges with emoji and without it have same height */ + content: "\0"; + width: 0; + display: inline-block; + color: transparent; +} /* Black one-pixel outline around emoji shapes */ .emoji { text-shadow: 1px 0 0 black, -1px 0 0 black, - 0 1px 0 black, + 0 1px 0 black, 0 -1px 0 black; } @@ -1478,7 +1575,20 @@ a.tooltip:hover::after { margin-left: 4px; display: flex; } -#settings-menu > a, #help-button > a { +#sidebar-button { + display: none; +} +.hide-sidebar #sidebar-button { + display: flex; + margin-right: 4px; + position: fixed; + left: 6px; + height: 34px; + width: 34px; + background-color: var(--main-background-color); + z-index: 1; +} +#settings-menu > a, #help-button > a, #sidebar-button > a { display: flex; align-items: center; justify-content: center; @@ -1493,10 +1603,21 @@ a.tooltip:hover::after { } #settings-menu > a:hover, #settings-menu > a:focus, -#help-button > a:hover, #help-button > a:focus { +#help-button > a:hover, #help-button > a:focus, +#sidebar-button > a:hover, #sidebar-button > a:focus { border-color: var(--settings-button-border-focus); } +#sidebar-button > a:before { + content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \ + fill="none" stroke="black">\ + <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5"/>\ + <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\ + <path d="m7.6121 3v16 M5.375 7.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>'); + width: 22px; + height: 22px; +} + #copy-path { color: var(--copy-path-button-color); background: var(--main-background-color); @@ -1711,7 +1832,7 @@ However, it's not needed with smaller screen width because the doc/code block is /* WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY If you update this line, then you also need to update the line with the same warning -in src-script.js +in src-script.js and main.js */ @media (max-width: 700px) { /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar, @@ -1722,6 +1843,10 @@ in src-script.js scroll-margin-top: 45px; } + .hide-sidebar #sidebar-button { + position: static; + } + .rustdoc { /* Sidebar should overlay main content, rather than pushing main content to the right. Turn off `display: flex` on the body element. */ @@ -1750,7 +1875,8 @@ in src-script.js /* Hide the logo and item name from the sidebar. Those are displayed in the mobile-topbar instead. */ .sidebar .logo-container, - .sidebar .location { + .sidebar .location, + .sidebar-resizer { display: none; } @@ -1818,6 +1944,10 @@ in src-script.js top: 0; } + .hide-sidebar .mobile-topbar { + display: none; + } + .sidebar-menu-toggle { width: 45px; /* Rare exception to specifying font sizes in rem. Since this is acting @@ -1827,6 +1957,10 @@ in src-script.js color: var(--main-color); } + .hide-sidebar .sidebar-menu-toggle { + display: none; + } + .sidebar-elems { margin-top: 1em; } @@ -1870,6 +2004,17 @@ in src-script.js display: none; } + /* sidebar button becomes topbar button */ + #sidebar-button > a:before { + content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \ + viewBox="0 0 22 22" fill="none" stroke="black">\ + <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5"/>\ + <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\ + <path d="m3 7.375h16m0-3h-4" stroke-width="1.25"/></svg>'); + width: 22px; + height: 22px; + } + /* Display an alternating layout on tablets and phones */ .item-table, .item-row, .item-table > li, .item-table > li > div, .search-results > a, .search-results > a > div { @@ -2274,6 +2419,8 @@ in src-script.js --scrape-example-help-hover-color: #000; --scrape-example-code-wrapper-background-start: rgba(255, 255, 255, 1); --scrape-example-code-wrapper-background-end: rgba(255, 255, 255, 0); + --sidebar-resizer-hover: hsl(207, 90%, 66%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: light */ @@ -2379,6 +2526,8 @@ in src-script.js --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(53, 53, 53, 1); --scrape-example-code-wrapper-background-end: rgba(53, 53, 53, 0); + --sidebar-resizer-hover: hsl(207, 30%, 54%); + --sidebar-resizer-active: hsl(207, 90%, 54%); } /* End theme: dark */ @@ -2488,6 +2637,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) --scrape-example-help-hover-color: #fff; --scrape-example-code-wrapper-background-start: rgba(15, 20, 25, 1); --scrape-example-code-wrapper-background-end: rgba(15, 20, 25, 0); + --sidebar-resizer-hover: hsl(34, 50%, 33%); + --sidebar-resizer-active: hsl(34, 100%, 66%); } :root[data-theme="ayu"] h1, @@ -2519,6 +2670,7 @@ Original by Dempfi (https://github.com/dempfi/ayu) } :root[data-theme="ayu"] .sidebar .current, +:root[data-theme="ayu"] .sidebar .current a, :root[data-theme="ayu"] .sidebar a:hover, :root[data-theme="ayu"] #src-sidebar div.files > a:hover, :root[data-theme="ayu"] details.dir-entry summary:hover, @@ -2569,7 +2721,8 @@ Original by Dempfi (https://github.com/dempfi/ayu) border-bottom: 1px solid rgba(242, 151, 24, 0.3); } -:root[data-theme="ayu"] #settings-menu > a img { +:root[data-theme="ayu"] #settings-menu > a img, +:root[data-theme="ayu"] #sidebar-button > a:before { filter: invert(100); } /* End theme: ayu */ diff --git a/src/librustdoc/html/static/js/externs.js b/src/librustdoc/html/static/js/externs.js index c7811b43d..93709e4e8 100644 --- a/src/librustdoc/html/static/js/externs.js +++ b/src/librustdoc/html/static/js/externs.js @@ -14,6 +14,7 @@ function initSearch(searchIndex){} * pathWithoutLast: Array<string>, * pathLast: string, * generics: Array<QueryElement>, + * bindings: Map<integer, Array<QueryElement>>, * }} */ let QueryElement; @@ -24,6 +25,7 @@ let QueryElement; * totalElems: number, * typeFilter: (null|string), * userQuery: string, + * isInBinding: (null|string), * }} */ let ParserState; @@ -40,6 +42,7 @@ let ParserState; * totalElems: number, * literalSearch: boolean, * corrections: Array<{from: string, to: integer}>, + * typeFingerprint: Uint32Array, * }} */ let ParsedQuery; @@ -191,8 +194,9 @@ let FunctionSearchType; /** * @typedef {{ * id: (null|number), - * ty: (null|number), + * ty: number, * generics: Array<FunctionType>, + * bindings: Map<integer, Array<FunctionType>>, * }} */ let FunctionType; diff --git a/src/librustdoc/html/static/js/main.js b/src/librustdoc/html/static/js/main.js index 7c052606a..63ab56053 100644 --- a/src/librustdoc/html/static/js/main.js +++ b/src/librustdoc/html/static/js/main.js @@ -1,5 +1,5 @@ // Local js definitions: -/* global addClass, getSettingValue, hasClass, searchState */ +/* global addClass, getSettingValue, hasClass, searchState, updateLocalStorage */ /* global onEach, onEachLazy, removeClass, getVar */ "use strict"; @@ -25,19 +25,9 @@ function showMain() { removeClass(document.getElementById(MAIN_ID), "hidden"); } -function elemIsInParent(elem, parent) { - while (elem && elem !== document.body) { - if (elem === parent) { - return true; - } - elem = elem.parentElement; - } - return false; -} - function blurHandler(event, parentElem, hideCallback) { - if (!elemIsInParent(document.activeElement, parentElem) && - !elemIsInParent(event.relatedTarget, parentElem) + if (!parentElem.contains(document.activeElement) && + !parentElem.contains(event.relatedTarget) ) { hideCallback(); } @@ -54,7 +44,7 @@ function setMobileTopbar() { if (mobileTopbar) { const mobileTitle = document.createElement("h2"); mobileTitle.className = "location"; - if (hasClass(document.body, "crate")) { + if (hasClass(document.querySelector(".rustdoc"), "crate")) { mobileTitle.innerText = `Crate ${window.currentCrate}`; } else if (locationTitle) { mobileTitle.innerHTML = locationTitle.innerHTML; @@ -485,7 +475,7 @@ function preLoadCss(cssUrl) { return; } - const modpath = hasClass(document.body, "mod") ? "../" : ""; + const modpath = hasClass(document.querySelector(".rustdoc"), "mod") ? "../" : ""; const h3 = document.createElement("h3"); h3.innerHTML = `<a href="${modpath}index.html#${id}">${longty}</a>`; @@ -505,7 +495,7 @@ function preLoadCss(cssUrl) { } const link = document.createElement("a"); link.href = path; - if (link.href === current_page) { + if (path === current_page) { link.className = "current"; } link.textContent = name; @@ -867,12 +857,12 @@ function preLoadCss(cssUrl) { for (const crate of window.ALL_CRATES) { const link = document.createElement("a"); link.href = window.rootPath + crate + "/index.html"; - if (window.rootPath !== "./" && crate === window.currentCrate) { - link.className = "current"; - } link.textContent = crate; const li = document.createElement("li"); + if (window.rootPath !== "./" && crate === window.currentCrate) { + li.className = "current"; + } li.appendChild(link); ul.appendChild(li); } @@ -1118,7 +1108,7 @@ function preLoadCss(cssUrl) { if (ev.pointerType !== "mouse") { return; } - if (!e.TOOLTIP_FORCE_VISIBLE && !elemIsInParent(ev.relatedTarget, e)) { + if (!e.TOOLTIP_FORCE_VISIBLE && !e.contains(ev.relatedTarget)) { // See "Tooltip pointer leave gesture" below. setTooltipHoverTimeout(e, false); addClass(wrapper, "fade-out"); @@ -1178,10 +1168,10 @@ function preLoadCss(cssUrl) { function tooltipBlurHandler(event) { if (window.CURRENT_TOOLTIP_ELEMENT && - !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT) && - !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT) && - !elemIsInParent(document.activeElement, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE) && - !elemIsInParent(event.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE) + !window.CURRENT_TOOLTIP_ELEMENT.contains(document.activeElement) && + !window.CURRENT_TOOLTIP_ELEMENT.contains(event.relatedTarget) && + !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(document.activeElement) && + !window.CURRENT_TOOLTIP_ELEMENT.TOOLTIP_BASE.contains(event.relatedTarget) ) { // Work around a difference in the focus behaviour between Firefox, Chrome, and Safari. // When I click the button on an already-opened tooltip popover, Safari @@ -1248,8 +1238,8 @@ function preLoadCss(cssUrl) { if (ev.pointerType !== "mouse") { return; } - if (!e.TOOLTIP_FORCE_VISIBLE && - !elemIsInParent(ev.relatedTarget, window.CURRENT_TOOLTIP_ELEMENT)) { + if (!e.TOOLTIP_FORCE_VISIBLE && window.CURRENT_TOOLTIP_ELEMENT && + !window.CURRENT_TOOLTIP_ELEMENT.contains(ev.relatedTarget)) { // Tooltip pointer leave gesture: // // Designing a good hover microinteraction is a matter of guessing user @@ -1328,8 +1318,7 @@ function preLoadCss(cssUrl) { const infos = [ `For a full list of all search features, take a look <a \ -href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ -#the-search-interface">here</a>.`, +href="https://doc.rust-lang.org/${channel}/rustdoc/read-documentation/search.html">here</a>.`, "Prefix searches with a type followed by a colon (e.g., <code>fn:</code>) to \ restrict the search to a given item kind.", "Accepted kinds are: <code>fn</code>, <code>mod</code>, <code>struct</code>, \ @@ -1484,6 +1473,264 @@ href="https://doc.rust-lang.org/${channel}/rustdoc/how-to-read-rustdoc.html\ searchState.setup(); }()); +// Hide, show, and resize the sidebar +// +// The body class and CSS variable are initially set up in storage.js, +// but in this file, we implement: +// +// * the show sidebar button, which appears if the sidebar is hidden +// and, by clicking on it, will bring it back +// * the sidebar resize handle, which appears only on large viewports +// with a [fine precision pointer] to allow the user to change +// the size of the sidebar +// +// [fine precision pointer]: https://developer.mozilla.org/en-US/docs/Web/CSS/@media/pointer +(function() { + // 100 is the size of the logo + // don't let the sidebar get smaller than that, or it'll get squished + const SIDEBAR_MIN = 100; + // Don't let the sidebar get bigger than this + const SIDEBAR_MAX = 500; + // Don't let the body (including the gutter) get smaller than this + // + // WARNING: RUSTDOC_MOBILE_BREAKPOINT MEDIA QUERY + // Acceptable values for BODY_MIN are constrained by the mobile breakpoint + // (which is the minimum size of the whole page where the sidebar exists) + // and the default sidebar width: + // + // BODY_MIN <= RUSTDOC_MOBILE_BREAKPOINT - DEFAULT_SIDEBAR_WIDTH + // + // At the time of this writing, the DEFAULT_SIDEBAR_WIDTH on src pages is + // 300px, and the RUSTDOC_MOBILE_BREAKPOINT is 700px, so BODY_MIN must be + // at most 400px. Otherwise, it would start out at the default size, then + // grabbing the resize handle would suddenly cause it to jank to + // its contraint-generated maximum. + const RUSTDOC_MOBILE_BREAKPOINT = 700; + const BODY_MIN = 400; + // At half-way past the minimum size, vanish the sidebar entirely + const SIDEBAR_VANISH_THRESHOLD = SIDEBAR_MIN / 2; + + // Toolbar button to show the sidebar. + // + // On small, "mobile-sized" viewports, it's not persistent and it + // can only be activated by going into Settings and hiding the nav bar. + // On larger, "desktop-sized" viewports (though that includes many + // tablets), it's fixed-position, appears in the left side margin, + // and it can be activated by resizing the sidebar into nothing. + const sidebarButton = document.getElementById("sidebar-button"); + if (sidebarButton) { + sidebarButton.addEventListener("click", e => { + removeClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "false"); + e.preventDefault(); + }); + } + + // Pointer capture. + // + // Resizing is a single-pointer gesture. Any secondary pointer is ignored + let currentPointerId = null; + + // "Desired" sidebar size. + // + // This is stashed here for window resizing. If the sidebar gets + // shrunk to maintain BODY_MIN, and then the user grows the window again, + // it gets the sidebar to restore its size. + let desiredSidebarSize = null; + + // Sidebar resize debouncer. + // + // The sidebar itself is resized instantly, but the body HTML can be too + // big for that, causing reflow jank. To reduce this, we queue up a separate + // animation frame and throttle it. + let pendingSidebarResizingFrame = false; + + // If this page has no sidebar at all, bail out. + const resizer = document.querySelector(".sidebar-resizer"); + const sidebar = document.querySelector(".sidebar"); + if (!resizer || !sidebar) { + return; + } + + // src page and docs page use different variables, because the contents of + // the sidebar are so different that it's reasonable to thing the user + // would want them to have different sizes + const isSrcPage = hasClass(document.body, "src"); + + // Call this function to hide the sidebar when using the resize handle + // + // This function also nulls out the sidebar width CSS variable and setting, + // causing it to return to its default. This does not happen if you do it + // from settings.js, which uses a separate function. It's done here because + // the minimum sidebar size is rather uncomfortable, and it must pass + // through that size when using the shrink-to-nothing gesture. + function hideSidebar() { + if (isSrcPage) { + window.rustdocCloseSourceSidebar(); + updateLocalStorage("src-sidebar-width", null); + // [RUSTDOCIMPL] CSS variable fast path + // + // The sidebar width variable is attached to the <html> element by + // storage.js, because the sidebar and resizer don't exist yet. + // But the resize code, in `resize()`, sets the property on the + // sidebar and resizer elements (which are the only elements that + // use the variable) to avoid recalculating CSS on the entire + // document on every frame. + // + // So, to clear it, we need to clear all three. + document.documentElement.style.removeProperty("--src-sidebar-width"); + sidebar.style.removeProperty("--src-sidebar-width"); + resizer.style.removeProperty("--src-sidebar-width"); + } else { + addClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "true"); + updateLocalStorage("desktop-sidebar-width", null); + document.documentElement.style.removeProperty("--desktop-sidebar-width"); + sidebar.style.removeProperty("--desktop-sidebar-width"); + resizer.style.removeProperty("--desktop-sidebar-width"); + } + } + + // Call this function to show the sidebar from the resize handle. + // On docs pages, this can only happen if the user has grabbed the resize + // handle, shrunk the sidebar down to nothing, and then pulls back into + // the visible range without releasing it. You can, however, grab the + // resize handle on a source page with the sidebar closed, because it + // remains visible all the time on there. + function showSidebar() { + if (isSrcPage) { + window.rustdocShowSourceSidebar(); + } else { + removeClass(document.documentElement, "hide-sidebar"); + updateLocalStorage("hide-sidebar", "false"); + } + } + + /** + * Call this to set the correct CSS variable and setting. + * This function doesn't enforce size constraints. Do that before calling it! + * + * @param {number} size - CSS px width of the sidebar. + */ + function changeSidebarSize(size) { + if (isSrcPage) { + updateLocalStorage("src-sidebar-width", size); + // [RUSTDOCIMPL] CSS variable fast path + // + // While this property is set on the HTML element at load time, + // because the sidebar isn't actually loaded yet, + // we scope this update to the sidebar to avoid hitting a slow + // path in WebKit. + sidebar.style.setProperty("--src-sidebar-width", size + "px"); + resizer.style.setProperty("--src-sidebar-width", size + "px"); + } else { + updateLocalStorage("desktop-sidebar-width", size); + sidebar.style.setProperty("--desktop-sidebar-width", size + "px"); + resizer.style.setProperty("--desktop-sidebar-width", size + "px"); + } + } + + // Check if the sidebar is hidden. Since src pages and doc pages have + // different settings, this function has to check that. + function isSidebarHidden() { + return isSrcPage ? + !hasClass(document.documentElement, "src-sidebar-expanded") : + hasClass(document.documentElement, "hide-sidebar"); + } + + // Respond to the resize handle event. + // This function enforces size constraints, and implements the + // shrink-to-nothing gesture based on thresholds defined above. + function resize(e) { + if (currentPointerId === null || currentPointerId !== e.pointerId) { + return; + } + e.preventDefault(); + const pos = e.clientX - sidebar.offsetLeft - 3; + if (pos < SIDEBAR_VANISH_THRESHOLD) { + hideSidebar(); + } else if (pos >= SIDEBAR_MIN) { + if (isSidebarHidden()) { + showSidebar(); + } + // don't let the sidebar get wider than SIDEBAR_MAX, or the body narrower + // than BODY_MIN + const constrainedPos = Math.min(pos, window.innerWidth - BODY_MIN, SIDEBAR_MAX); + changeSidebarSize(constrainedPos); + desiredSidebarSize = constrainedPos; + if (pendingSidebarResizingFrame !== false) { + clearTimeout(pendingSidebarResizingFrame); + } + pendingSidebarResizingFrame = setTimeout(() => { + if (currentPointerId === null || pendingSidebarResizingFrame === false) { + return; + } + pendingSidebarResizingFrame = false; + document.documentElement.style.setProperty( + "--resizing-sidebar-width", + desiredSidebarSize + "px" + ); + }, 100); + } + } + // Respond to the window resize event. + window.addEventListener("resize", () => { + if (window.innerWidth < RUSTDOC_MOBILE_BREAKPOINT) { + return; + } + stopResize(); + if (desiredSidebarSize >= (window.innerWidth - BODY_MIN)) { + changeSidebarSize(window.innerWidth - BODY_MIN); + } else if (desiredSidebarSize !== null && desiredSidebarSize > SIDEBAR_MIN) { + changeSidebarSize(desiredSidebarSize); + } + }); + function stopResize(e) { + if (currentPointerId === null) { + return; + } + if (e) { + e.preventDefault(); + } + desiredSidebarSize = sidebar.getBoundingClientRect().width; + removeClass(resizer, "active"); + window.removeEventListener("pointermove", resize, false); + window.removeEventListener("pointerup", stopResize, false); + removeClass(document.documentElement, "sidebar-resizing"); + document.documentElement.style.removeProperty( "--resizing-sidebar-width"); + if (resizer.releasePointerCapture) { + resizer.releasePointerCapture(currentPointerId); + currentPointerId = null; + } + } + function initResize(e) { + if (currentPointerId !== null || e.altKey || e.ctrlKey || e.metaKey || e.button !== 0) { + return; + } + if (resizer.setPointerCapture) { + resizer.setPointerCapture(e.pointerId); + if (!resizer.hasPointerCapture(e.pointerId)) { + // unable to capture pointer; something else has it + // on iOS, this usually means you long-clicked a link instead + resizer.releasePointerCapture(e.pointerId); + return; + } + currentPointerId = e.pointerId; + } + e.preventDefault(); + window.addEventListener("pointermove", resize, false); + window.addEventListener("pointercancel", stopResize, false); + window.addEventListener("pointerup", stopResize, false); + addClass(resizer, "active"); + addClass(document.documentElement, "sidebar-resizing"); + const pos = e.clientX - sidebar.offsetLeft - 3; + document.documentElement.style.setProperty( "--resizing-sidebar-width", pos + "px"); + desiredSidebarSize = null; + } + resizer.addEventListener("pointerdown", initResize, false); +}()); + +// This section handles the copy button that appears next to the path breadcrumbs (function() { let reset_button_timeout = null; diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 48c9a53a2..e824a1fd4 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -18,36 +18,38 @@ if (!Array.prototype.toSpliced) { // This mapping table should match the discriminants of // `rustdoc::formats::item_type::ItemType` type in Rust. const itemTypes = [ + "keyword", + "primitive", "mod", "externcrate", "import", - "struct", + "struct", // 5 "enum", "fn", "type", "static", - "trait", + "trait", // 10 "impl", "tymethod", "method", "structfield", - "variant", + "variant", // 15 "macro", - "primitive", "associatedtype", "constant", "associatedconstant", - "union", + "union", // 20 "foreigntype", - "keyword", "existential", "attr", "derive", - "traitalias", + "traitalias", // 25 "generic", ]; const longItemTypes = [ + "keyword", + "primitive type", "module", "extern crate", "re-export", @@ -63,13 +65,11 @@ const longItemTypes = [ "struct field", "enum variant", "macro", - "primitive type", "assoc type", "constant", "assoc const", "union", "foreign type", - "keyword", "existential type", "attribute macro", "derive macro", @@ -77,15 +77,9 @@ const longItemTypes = [ ]; // used for special search precedence -const TY_PRIMITIVE = itemTypes.indexOf("primitive"); -const TY_KEYWORD = itemTypes.indexOf("keyword"); const TY_GENERIC = itemTypes.indexOf("generic"); const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../"; -function hasOwnPropertyRustdoc(obj, property) { - return Object.prototype.hasOwnProperty.call(obj, property); -} - // In the search display, allows to switch between tabs. function printTab(nb) { let iter = 0; @@ -240,12 +234,16 @@ function initSearch(rawSearchIndex) { * @type {Array<Row>} */ let searchIndex; + /** + * @type {Uint32Array} + */ + let functionTypeFingerprint; let currentResults; /** * Map from normalized type names to integers. Used to make type search * more efficient. * - * @type {Map<string, integer>} + * @type {Map<string, {id: integer, assocOnly: boolean}>} */ let typeNameIdMap; const ALIASES = new Map(); @@ -272,37 +270,32 @@ function initSearch(rawSearchIndex) { * get the same ID. * * @param {string} name + * @param {boolean} isAssocType - True if this is an assoc type * * @returns {integer} */ - function buildTypeMapIndex(name) { + function buildTypeMapIndex(name, isAssocType) { if (name === "" || name === null) { return null; } if (typeNameIdMap.has(name)) { - return typeNameIdMap.get(name); + const obj = typeNameIdMap.get(name); + obj.assocOnly = isAssocType && obj.assocOnly; + return obj.id; } else { const id = typeNameIdMap.size; - typeNameIdMap.set(name, id); + typeNameIdMap.set(name, {id, assocOnly: isAssocType}); return id; } } - function isWhitespace(c) { - return " \t\n\r".indexOf(c) !== -1; - } - function isSpecialStartCharacter(c) { return "<\"".indexOf(c) !== -1; } function isEndCharacter(c) { - return ",>-]".indexOf(c) !== -1; - } - - function isStopCharacter(c) { - return isEndCharacter(c); + return "=,>-]".indexOf(c) !== -1; } function isErrorCharacter(c) { @@ -398,7 +391,7 @@ function initSearch(rawSearchIndex) { * @return {boolean} */ function isSeparatorCharacter(c) { - return c === ","; + return c === "," || c === "="; } /** @@ -410,7 +403,7 @@ function initSearch(rawSearchIndex) { * @return {boolean} */ function isPathSeparator(c) { - return c === ":" || isWhitespace(c); + return c === ":" || c === " "; } /** @@ -427,7 +420,7 @@ function initSearch(rawSearchIndex) { const c = parserState.userQuery[pos - 1]; if (c === lookingFor) { return true; - } else if (!isWhitespace(c)) { + } else if (c !== " ") { break; } pos -= 1; @@ -456,7 +449,7 @@ function initSearch(rawSearchIndex) { function skipWhitespace(parserState) { while (parserState.pos < parserState.userQuery.length) { const c = parserState.userQuery[parserState.pos]; - if (!isWhitespace(c)) { + if (c !== " ") { break; } parserState.pos += 1; @@ -475,8 +468,6 @@ function initSearch(rawSearchIndex) { const path = name.trim(); if (path.length === 0 && generics.length === 0) { throw ["Unexpected ", parserState.userQuery[parserState.pos]]; - } else if (path === "*") { - throw ["Unexpected ", "*"]; } if (query.literalSearch && parserState.totalElems - parserState.genericsElems > 0) { throw ["Cannot have more than one element if you use quotes"]; @@ -500,28 +491,30 @@ function initSearch(rawSearchIndex) { " does not accept generic parameters", ]; } + const bindingName = parserState.isInBinding; + parserState.isInBinding = null; return { name: "never", id: null, fullPath: ["never"], pathWithoutLast: [], pathLast: "never", + normalizedPathLast: "never", generics: [], + bindings: new Map(), typeFilter: "primitive", + bindingName, }; } + const quadcolon = /::\s*::/.exec(path); if (path.startsWith("::")) { throw ["Paths cannot start with ", "::"]; } else if (path.endsWith("::")) { throw ["Paths cannot end with ", "::"]; - } else if (path.includes("::::")) { - throw ["Unexpected ", "::::"]; - } else if (path.includes(" ::")) { - throw ["Unexpected ", " ::"]; - } else if (path.includes(":: ")) { - throw ["Unexpected ", ":: "]; - } - const pathSegments = path.split(/::|\s+/); + } else if (quadcolon !== null) { + throw ["Unexpected ", quadcolon[0]]; + } + const pathSegments = path.split(/(?:::\s*)|(?:\s+(?:::\s*)?)/); // In case we only have something like `<p>`, there is no name. if (pathSegments.length === 0 || (pathSegments.length === 1 && pathSegments[0] === "")) { if (generics.length > 0 || prevIs(parserState, ">")) { @@ -542,14 +535,29 @@ function initSearch(rawSearchIndex) { if (isInGenerics) { parserState.genericsElems += 1; } + const bindingName = parserState.isInBinding; + parserState.isInBinding = null; + const bindings = new Map(); + const pathLast = pathSegments[pathSegments.length - 1]; return { name: name.trim(), id: null, fullPath: pathSegments, pathWithoutLast: pathSegments.slice(0, pathSegments.length - 1), - pathLast: pathSegments[pathSegments.length - 1], - generics: generics, + pathLast, + normalizedPathLast: pathLast.replace(/_/g, ""), + generics: generics.filter(gen => { + // Syntactically, bindings are parsed as generics, + // but the query engine treats them differently. + if (gen.bindingName !== null) { + bindings.set(gen.bindingName.name, [gen, ...gen.bindingName.generics]); + return false; + } + return true; + }), + bindings, typeFilter, + bindingName, }; } @@ -589,7 +597,7 @@ function initSearch(rawSearchIndex) { } else { while (parserState.pos + 1 < parserState.length) { const next_c = parserState.userQuery[parserState.pos + 1]; - if (!isWhitespace(next_c)) { + if (next_c !== " ") { break; } parserState.pos += 1; @@ -608,7 +616,7 @@ function initSearch(rawSearchIndex) { } } else if ( c === "[" || - isStopCharacter(c) || + isEndCharacter(c) || isSpecialStartCharacter(c) || isSeparatorCharacter(c) ) { @@ -657,6 +665,7 @@ function initSearch(rawSearchIndex) { parserState.pos += 1; getItemsBefore(query, parserState, generics, "]"); const typeFilter = parserState.typeFilter; + const isInBinding = parserState.isInBinding; if (typeFilter !== null && typeFilter !== "primitive") { throw [ "Invalid search type: primitive ", @@ -667,18 +676,27 @@ function initSearch(rawSearchIndex) { ]; } parserState.typeFilter = null; + parserState.isInBinding = null; parserState.totalElems += 1; if (isInGenerics) { parserState.genericsElems += 1; } + for (const gen of generics) { + if (gen.bindingName !== null) { + throw ["Type parameter ", "=", " cannot be within slice ", "[]"]; + } + } elems.push({ name: "[]", id: null, fullPath: ["[]"], pathWithoutLast: [], pathLast: "[]", + normalizedPathLast: "[]", generics, typeFilter: "primitive", + bindingName: isInBinding, + bindings: new Map(), }); } else { const isStringElem = parserState.userQuery[start] === "\""; @@ -705,15 +723,38 @@ function initSearch(rawSearchIndex) { if (start >= end && generics.length === 0) { return; } - elems.push( - createQueryElement( - query, - parserState, - parserState.userQuery.slice(start, end), - generics, - isInGenerics - ) - ); + if (parserState.userQuery[parserState.pos] === "=") { + if (parserState.isInBinding) { + throw ["Cannot write ", "=", " twice in a binding"]; + } + if (!isInGenerics) { + throw ["Type parameter ", "=", " must be within generics list"]; + } + const name = parserState.userQuery.slice(start, end).trim(); + if (name === "!") { + throw ["Type parameter ", "=", " key cannot be ", "!", " never type"]; + } + if (name.includes("!")) { + throw ["Type parameter ", "=", " key cannot be ", "!", " macro"]; + } + if (name.includes("::")) { + throw ["Type parameter ", "=", " key cannot contain ", "::", " path"]; + } + if (name.includes(":")) { + throw ["Type parameter ", "=", " key cannot contain ", ":", " type"]; + } + parserState.isInBinding = { name, generics }; + } else { + elems.push( + createQueryElement( + query, + parserState, + parserState.userQuery.slice(start, end), + generics, + isInGenerics + ) + ); + } } } @@ -737,6 +778,8 @@ function initSearch(rawSearchIndex) { // If this is a generic, keep the outer item's type filter around. const oldTypeFilter = parserState.typeFilter; parserState.typeFilter = null; + const oldIsInBinding = parserState.isInBinding; + parserState.isInBinding = null; let extra = ""; if (endChar === ">") { @@ -752,6 +795,9 @@ function initSearch(rawSearchIndex) { while (parserState.pos < parserState.length) { const c = parserState.userQuery[parserState.pos]; if (c === endChar) { + if (parserState.isInBinding) { + throw ["Unexpected ", endChar, " after ", "="]; + } break; } else if (isSeparatorCharacter(c)) { parserState.pos += 1; @@ -791,7 +837,9 @@ function initSearch(rawSearchIndex) { throw [ "Expected ", ",", - " or ", + ", ", + "=", + ", or ", endChar, ...extra, ", found ", @@ -801,6 +849,8 @@ function initSearch(rawSearchIndex) { throw [ "Expected ", ",", + " or ", + "=", ...extra, ", found ", c, @@ -828,6 +878,7 @@ function initSearch(rawSearchIndex) { parserState.pos += 1; parserState.typeFilter = oldTypeFilter; + parserState.isInBinding = oldIsInBinding; } /** @@ -865,7 +916,7 @@ function initSearch(rawSearchIndex) { while (parserState.pos < parserState.length) { const c = parserState.userQuery[parserState.pos]; - if (isStopCharacter(c)) { + if (isEndCharacter(c)) { foundStopChar = true; if (isSeparatorCharacter(c)) { parserState.pos += 1; @@ -900,7 +951,7 @@ function initSearch(rawSearchIndex) { query.literalSearch = false; foundStopChar = true; continue; - } else if (isWhitespace(c)) { + } else if (c === " ") { skipWhitespace(parserState); continue; } @@ -991,6 +1042,8 @@ function initSearch(rawSearchIndex) { correction: null, proposeCorrectionFrom: null, proposeCorrectionTo: null, + // bloom filter build from type ids + typeFingerprint: new Uint32Array(4), }; } @@ -1021,7 +1074,7 @@ function initSearch(rawSearchIndex) { if (elem && elem.value !== "all crates" && - hasOwnPropertyRustdoc(rawSearchIndex, elem.value) + rawSearchIndex.has(elem.value) ) { return elem.value; } @@ -1054,8 +1107,13 @@ function initSearch(rawSearchIndex) { for (const elem2 of elem.generics) { convertTypeFilterOnElem(elem2); } + for (const constraints of elem.bindings.values()) { + for (const constraint of constraints) { + convertTypeFilterOnElem(constraint); + } + } } - userQuery = userQuery.trim(); + userQuery = userQuery.trim().replace(/\r|\n|\t/g, " "); const parserState = { length: userQuery.length, pos: 0, @@ -1063,6 +1121,7 @@ function initSearch(rawSearchIndex) { totalElems: 0, genericsElems: 0, typeFilter: null, + isInBinding: null, userQuery: userQuery.toLowerCase(), }; let query = newParsedQuery(userQuery); @@ -1080,7 +1139,6 @@ function initSearch(rawSearchIndex) { query.error = err; return query; } - if (!query.literalSearch) { // If there is more than one element in the query, we switch to literalSearch in any // case. @@ -1114,13 +1172,12 @@ function initSearch(rawSearchIndex) { * Executes the parsed query and builds a {ResultsTable}. * * @param {ParsedQuery} parsedQuery - The parsed user query - * @param {Object} searchWords - The list of search words to query against * @param {Object} [filterCrates] - Crate to search in if defined * @param {Object} [currentCrate] - Current crate, to rank results from this crate higher * * @return {ResultsTable} */ - function execQuery(parsedQuery, searchWords, filterCrates, currentCrate) { + function execQuery(parsedQuery, filterCrates, currentCrate) { const results_others = new Map(), results_in_args = new Map(), results_returned = new Map(); @@ -1178,8 +1235,8 @@ function initSearch(rawSearchIndex) { const userQuery = parsedQuery.userQuery; const result_list = []; for (const result of results.values()) { - result.word = searchWords[result.id]; - result.item = searchIndex[result.id] || {}; + result.item = searchIndex[result.id]; + result.word = searchIndex[result.id].word; result_list.push(result); } @@ -1251,16 +1308,6 @@ function initSearch(rawSearchIndex) { return (a > b ? +1 : -1); } - // special precedence for primitive and keyword pages - if ((aaa.item.ty === TY_PRIMITIVE && bbb.item.ty !== TY_KEYWORD) || - (aaa.item.ty === TY_KEYWORD && bbb.item.ty !== TY_PRIMITIVE)) { - return -1; - } - if ((bbb.item.ty === TY_PRIMITIVE && aaa.item.ty !== TY_PRIMITIVE) || - (bbb.item.ty === TY_KEYWORD && aaa.item.ty !== TY_KEYWORD)) { - return 1; - } - // sort by description (no description goes later) a = (aaa.item.desc === ""); b = (bbb.item.desc === ""); @@ -1286,59 +1333,10 @@ function initSearch(rawSearchIndex) { return 0; }); - let nameSplit = null; - if (parsedQuery.elems.length === 1) { - const hasPath = typeof parsedQuery.elems[0].path === "undefined"; - nameSplit = hasPath ? null : parsedQuery.elems[0].path; - } - - for (const result of result_list) { - // this validation does not make sense when searching by types - if (result.dontValidate) { - continue; - } - const name = result.item.name.toLowerCase(), - path = result.item.path.toLowerCase(), - parent = result.item.parent; - - if (!isType && !validateResult(name, path, nameSplit, parent)) { - result.id = -1; - } - } return transformResults(result_list); } /** - * This function checks generics in search query `queryElem` can all be found in the - * search index (`fnType`), - * - * This function returns `true` if it matches, and also writes the results to mgensInout. - * It returns `false` if no match is found, and leaves mgensInout untouched. - * - * @param {FunctionType} fnType - The object to check. - * @param {QueryElement} queryElem - The element from the parsed query. - * @param {[FunctionType]} whereClause - Trait bounds for generic items. - * @param {Map<number,number>|null} mgensInout - Map functions generics to query generics. - * - * @return {boolean} - Returns true if a match, false otherwise. - */ - function checkGenerics(fnType, queryElem, whereClause, mgensInout) { - return unifyFunctionTypes( - fnType.generics, - queryElem.generics, - whereClause, - mgensInout, - mgens => { - if (mgensInout) { - for (const [fid, qid] of mgens.entries()) { - mgensInout.set(fid, qid); - } - } - return true; - } - ); - } - /** * This function checks if a list of search query `queryElems` can all be found in the * search index (`fnTypes`). * @@ -1348,7 +1346,7 @@ function initSearch(rawSearchIndex) { * then this function will try with a different solution, or bail with false if it * runs out of candidates. * - * @param {Array<FunctionType>} fnTypes - The objects to check. + * @param {Array<FunctionType>} fnTypesIn - The objects to check. * @param {Array<QueryElement>} queryElems - The elements from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. * @param {Map<number,number>|null} mgensIn @@ -1359,9 +1357,9 @@ function initSearch(rawSearchIndex) { */ function unifyFunctionTypes(fnTypesIn, queryElems, whereClause, mgensIn, solutionCb) { /** - * @type Map<integer, integer> + * @type Map<integer, integer>|null */ - let mgens = new Map(mgensIn); + const mgens = mgensIn === null ? null : new Map(mgensIn); if (queryElems.length === 0) { return !solutionCb || solutionCb(mgens); } @@ -1369,204 +1367,249 @@ function initSearch(rawSearchIndex) { return false; } const ql = queryElems.length; - let fl = fnTypesIn.length; + const fl = fnTypesIn.length; + + // One element fast path / base case + if (ql === 1 && queryElems[0].generics.length === 0 + && queryElems[0].bindings.size === 0) { + const queryElem = queryElems[0]; + for (const fnType of fnTypesIn) { + if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { + continue; + } + if (fnType.id < 0 && queryElem.id < 0) { + if (mgens && mgens.has(fnType.id) && + mgens.get(fnType.id) !== queryElem.id) { + continue; + } + const mgensScratch = new Map(mgens); + mgensScratch.set(fnType.id, queryElem.id); + if (!solutionCb || solutionCb(mgensScratch)) { + return true; + } + } else if (!solutionCb || solutionCb(mgens ? new Map(mgens) : null)) { + // unifyFunctionTypeIsMatchCandidate already checks that ids match + return true; + } + } + for (const fnType of fnTypesIn) { + if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { + continue; + } + if (fnType.id < 0) { + if (mgens && mgens.has(fnType.id) && + mgens.get(fnType.id) !== 0) { + continue; + } + const mgensScratch = new Map(mgens); + mgensScratch.set(fnType.id, 0); + if (unifyFunctionTypes( + whereClause[(-fnType.id) - 1], + queryElems, + whereClause, + mgensScratch, + solutionCb + )) { + return true; + } + } else if (unifyFunctionTypes( + [...fnType.generics, ...Array.from(fnType.bindings.values()).flat() ], + queryElems, + whereClause, + mgens ? new Map(mgens) : null, + solutionCb + )) { + return true; + } + } + return false; + } + + // Multiple element recursive case /** * @type Array<FunctionType> */ - let fnTypes = fnTypesIn.slice(); + const fnTypes = fnTypesIn.slice(); /** - * loop works by building up a solution set in the working arrays + * Algorithm works by building up a solution set in the working arrays * fnTypes gets mutated in place to make this work, while queryElems - * is left alone + * is left alone. + * + * It works backwards, because arrays can be cheaply truncated that way. * - * vvvvvvv `i` points here - * queryElems = [ good, good, good, unknown, unknown ], - * fnTypes = [ good, good, good, unknown, unknown ], - * ---------------- ^^^^^^^^^^^^^^^^ `j` iterates after `i`, - * | looking for candidates - * everything before `i` is the - * current working solution + * vvvvvvv `queryElem` + * queryElems = [ unknown, unknown, good, good, good ] + * fnTypes = [ unknown, unknown, good, good, good ] + * ^^^^^^^^^^^^^^^^ loop over these elements to find candidates * * Everything in the current working solution is known to be a good * match, but it might not be the match we wind up going with, because * there might be more than one candidate match, and we need to try them all * before giving up. So, to handle this, it backtracks on failure. - * - * @type Array<{ - * "fnTypesScratch": Array<FunctionType>, - * "queryElemsOffset": integer, - * "fnTypesOffset": integer - * }> */ - const backtracking = []; - let i = 0; - let j = 0; - const backtrack = () => { - while (backtracking.length !== 0) { - // this session failed, but there are other possible solutions - // to backtrack, reset to (a copy of) the old array, do the swap or unboxing - const { - fnTypesScratch, - mgensScratch, - queryElemsOffset, - fnTypesOffset, - unbox, - } = backtracking.pop(); - mgens = new Map(mgensScratch); - const fnType = fnTypesScratch[fnTypesOffset]; - const queryElem = queryElems[queryElemsOffset]; - if (unbox) { - if (fnType.id < 0) { - if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { - continue; - } - mgens.set(fnType.id, 0); - } - const generics = fnType.id < 0 ? - whereClause[(-fnType.id) - 1] : - fnType.generics; - fnTypes = fnTypesScratch.toSpliced(fnTypesOffset, 1, ...generics); - fl = fnTypes.length; - // re-run the matching algorithm on this item - i = queryElemsOffset - 1; - } else { - if (fnType.id < 0) { - if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) { - continue; - } - mgens.set(fnType.id, queryElem.id); - } - fnTypes = fnTypesScratch.slice(); - fl = fnTypes.length; - const tmp = fnTypes[queryElemsOffset]; - fnTypes[queryElemsOffset] = fnTypes[fnTypesOffset]; - fnTypes[fnTypesOffset] = tmp; - // this is known as a good match; go to the next one - i = queryElemsOffset; - } - return true; + const flast = fl - 1; + const qlast = ql - 1; + const queryElem = queryElems[qlast]; + let queryElemsTmp = null; + for (let i = flast; i >= 0; i -= 1) { + const fnType = fnTypes[i]; + if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { + continue; } - return false; - }; - for (i = 0; i !== ql; ++i) { - const queryElem = queryElems[i]; - /** - * list of potential function types that go with the current query element. - * @type Array<integer> - */ - const matchCandidates = []; - let fnTypesScratch = null; - let mgensScratch = null; - // don't try anything before `i`, because they've already been - // paired off with the other query elements - for (j = i; j !== fl; ++j) { - const fnType = fnTypes[j]; - if (unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { - if (!fnTypesScratch) { - fnTypesScratch = fnTypes.slice(); + let mgensScratch; + if (fnType.id < 0) { + mgensScratch = new Map(mgens); + if (mgensScratch.has(fnType.id) + && mgensScratch.get(fnType.id) !== queryElem.id) { + continue; + } + mgensScratch.set(fnType.id, queryElem.id); + } else { + mgensScratch = mgens; + } + // fnTypes[i] is a potential match + // fnTypes[flast] is the last item in the list + // swap them, and drop the potential match from the list + // check if the remaining function types also match + fnTypes[i] = fnTypes[flast]; + fnTypes.length = flast; + if (!queryElemsTmp) { + queryElemsTmp = queryElems.slice(0, qlast); + } + const passesUnification = unifyFunctionTypes( + fnTypes, + queryElemsTmp, + whereClause, + mgensScratch, + mgensScratch => { + if (fnType.generics.length === 0 && queryElem.generics.length === 0 + && fnType.bindings.size === 0 && queryElem.bindings.size === 0) { + return !solutionCb || solutionCb(mgensScratch); } - unifyFunctionTypes( - fnType.generics, - queryElem.generics, + const solution = unifyFunctionTypeCheckBindings( + fnType, + queryElem, whereClause, - mgens, - mgensScratch => { - matchCandidates.push({ - fnTypesScratch, - mgensScratch, - queryElemsOffset: i, - fnTypesOffset: j, - unbox: false, - }); - return false; // "reject" all candidates to gather all of them - } + mgensScratch ); - } - if (unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { - if (!fnTypesScratch) { - fnTypesScratch = fnTypes.slice(); + if (!solution) { + return false; } - if (!mgensScratch) { - mgensScratch = new Map(mgens); + const simplifiedGenerics = solution.simplifiedGenerics; + for (const simplifiedMgens of solution.mgens) { + const passesUnification = unifyFunctionTypes( + simplifiedGenerics, + queryElem.generics, + whereClause, + simplifiedMgens, + solutionCb + ); + if (passesUnification) { + return true; + } } - backtracking.push({ - fnTypesScratch, - mgensScratch, - queryElemsOffset: i, - fnTypesOffset: j, - unbox: true, - }); - } - } - if (matchCandidates.length === 0) { - if (backtrack()) { - continue; - } else { return false; } + ); + if (passesUnification) { + return true; } - // use the current candidate - const {fnTypesOffset: candidate, mgensScratch: mgensNew} = matchCandidates.pop(); - if (fnTypes[candidate].id < 0 && queryElems[i].id < 0) { - mgens.set(fnTypes[candidate].id, queryElems[i].id); - } - for (const [fid, qid] of mgensNew) { - mgens.set(fid, qid); - } - // `i` and `j` are paired off - // `queryElems[i]` is left in place - // `fnTypes[j]` is swapped with `fnTypes[i]` to pair them off - const tmp = fnTypes[candidate]; - fnTypes[candidate] = fnTypes[i]; - fnTypes[i] = tmp; - // write other candidates to backtracking queue - for (const otherCandidate of matchCandidates) { - backtracking.push(otherCandidate); - } - // If we're on the last item, check the solution with the callback - // backtrack if the callback says its unsuitable - while (i === (ql - 1) && solutionCb && !solutionCb(mgens)) { - if (!backtrack()) { - return false; + // backtrack + fnTypes[flast] = fnTypes[i]; + fnTypes[i] = fnType; + fnTypes.length = fl; + } + for (let i = flast; i >= 0; i -= 1) { + const fnType = fnTypes[i]; + if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { + continue; + } + let mgensScratch; + if (fnType.id < 0) { + mgensScratch = new Map(mgens); + if (mgensScratch.has(fnType.id) && mgensScratch.get(fnType.id) !== 0) { + continue; } + mgensScratch.set(fnType.id, 0); + } else { + mgensScratch = mgens; + } + const generics = fnType.id < 0 ? + whereClause[(-fnType.id) - 1] : + fnType.generics; + const bindings = fnType.bindings ? + Array.from(fnType.bindings.values()).flat() : + []; + const passesUnification = unifyFunctionTypes( + fnTypes.toSpliced(i, 1, ...generics, ...bindings), + queryElems, + whereClause, + mgensScratch, + solutionCb + ); + if (passesUnification) { + return true; } } - return true; + return false; } - function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens) { + /** + * Check if this function is a match candidate. + * + * This function is all the fast checks that don't require backtracking. + * It checks that two items are not named differently, and is load-bearing for that. + * It also checks that, if the query has generics, the function type must have generics + * or associated type bindings: that's not load-bearing, but it prevents unnecessary + * backtracking later. + * + * @param {FunctionType} fnType + * @param {QueryElement} queryElem + * @param {[FunctionSearchType]} whereClause - Trait bounds for generic items. + * @param {Map<number,number>|null} mgensIn - Map functions generics to query generics. + * @returns {boolean} + */ + function unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgensIn) { // type filters look like `trait:Read` or `enum:Result` if (!typePassesFilter(queryElem.typeFilter, fnType.ty)) { return false; } // fnType.id < 0 means generic // queryElem.id < 0 does too - // mgens[fnType.id] = queryElem.id - // or, if mgens[fnType.id] = 0, then we've matched this generic with a bare trait + // mgensIn[fnType.id] = queryElem.id + // or, if mgensIn[fnType.id] = 0, then we've matched this generic with a bare trait // and should make that same decision everywhere it appears if (fnType.id < 0 && queryElem.id < 0) { - if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) { - return false; - } - for (const [fid, qid] of mgens.entries()) { - if (fnType.id !== fid && queryElem.id === qid) { + if (mgensIn) { + if (mgensIn.has(fnType.id) && mgensIn.get(fnType.id) !== queryElem.id) { return false; } - if (fnType.id === fid && queryElem.id !== qid) { - return false; + for (const [fid, qid] of mgensIn.entries()) { + if (fnType.id !== fid && queryElem.id === qid) { + return false; + } + if (fnType.id === fid && queryElem.id !== qid) { + return false; + } } } + return true; } else { if (queryElem.id === typeNameIdOfArrayOrSlice && (fnType.id === typeNameIdOfSlice || fnType.id === typeNameIdOfArray) ) { // [] matches primitive:array or primitive:slice // if it matches, then we're fine, and this is an appropriate match candidate - } else if (fnType.id !== queryElem.id) { + } else if (fnType.id !== queryElem.id || queryElem.id === null) { return false; } // If the query elem has generics, and the function doesn't, // it can't match. - if (fnType.generics.length === 0 && queryElem.generics.length !== 0) { + if ((fnType.generics.length + fnType.bindings.size) === 0 && + queryElem.generics.length !== 0 + ) { + return false; + } + if (fnType.bindings.size < queryElem.bindings.size) { return false; } // If the query element is a path (it contains `::`), we need to check if this @@ -1595,9 +1638,87 @@ function initSearch(rawSearchIndex) { return false; } } + return true; + } + } + /** + * This function checks the associated type bindings. Any that aren't matched get converted + * to generics, and this function returns an array of the function's generics with these + * simplified bindings added to them. That is, it takes a path like this: + * + * Iterator<Item=u32> + * + * ... if queryElem itself has an `Item=` in it, then this function returns an empty array. + * But if queryElem contains no Item=, then this function returns a one-item array with the + * ID of u32 in it, and the rest of the matching engine acts as if `Iterator<u32>` were + * the type instead. + * + * @param {FunctionType} fnType + * @param {QueryElement} queryElem + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map<number,number>} mgensIn - Map functions generics to query generics. + * Never modified. + * @returns {false|{mgens: [Map<number,number>], simplifiedGenerics: [FunctionType]}} + */ + function unifyFunctionTypeCheckBindings(fnType, queryElem, whereClause, mgensIn) { + if (fnType.bindings.size < queryElem.bindings.size) { + return false; + } + let simplifiedGenerics = fnType.generics || []; + if (fnType.bindings.size > 0) { + let mgensSolutionSet = [mgensIn]; + for (const [name, constraints] of queryElem.bindings.entries()) { + if (mgensSolutionSet.length === 0) { + return false; + } + if (!fnType.bindings.has(name)) { + return false; + } + const fnTypeBindings = fnType.bindings.get(name); + mgensSolutionSet = mgensSolutionSet.flatMap(mgens => { + const newSolutions = []; + unifyFunctionTypes( + fnTypeBindings, + constraints, + whereClause, + mgens, + newMgens => { + newSolutions.push(newMgens); + // return `false` makes unifyFunctionTypes return the full set of + // possible solutions + return false; + } + ); + return newSolutions; + }); + } + if (mgensSolutionSet.length === 0) { + return false; + } + const binds = Array.from(fnType.bindings.entries()).flatMap(entry => { + const [name, constraints] = entry; + if (queryElem.bindings.has(name)) { + return []; + } else { + return constraints; + } + }); + if (simplifiedGenerics.length > 0) { + simplifiedGenerics = [...simplifiedGenerics, ...binds]; + } else { + simplifiedGenerics = binds; + } + return { simplifiedGenerics, mgens: mgensSolutionSet }; } - return true; + return { simplifiedGenerics, mgens: [mgensIn] }; } + /** + * @param {FunctionType} fnType + * @param {QueryElement} queryElem + * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map<number,number>|null} mgens - Map functions generics to query generics. + * @returns {boolean} + */ function unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens) { if (fnType.id < 0 && queryElem.id >= 0) { if (!whereClause) { @@ -1605,16 +1726,29 @@ function initSearch(rawSearchIndex) { } // mgens[fnType.id] === 0 indicates that we committed to unboxing this generic // mgens[fnType.id] === null indicates that we haven't decided yet - if (mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { + if (mgens && mgens.has(fnType.id) && mgens.get(fnType.id) !== 0) { return false; } + // Where clauses can represent cyclical data. + // `null` prevents it from trying to unbox in an infinite loop + const mgensTmp = new Map(mgens); + mgensTmp.set(fnType.id, null); // This is only a potential unbox if the search query appears in the where clause // for example, searching `Read -> usize` should find // `fn read_all<R: Read>(R) -> Result<usize>` // generic `R` is considered "unboxed" - return checkIfInList(whereClause[(-fnType.id) - 1], queryElem, whereClause); - } else if (fnType.generics && fnType.generics.length > 0) { - return checkIfInList(fnType.generics, queryElem, whereClause); + return checkIfInList( + whereClause[(-fnType.id) - 1], + queryElem, + whereClause, + mgensTmp + ); + } else if (fnType.generics.length > 0 || fnType.bindings.size > 0) { + const simplifiedGenerics = [ + ...fnType.generics, + ...Array.from(fnType.bindings.values()).flat(), + ]; + return checkIfInList(simplifiedGenerics, queryElem, whereClause, mgens); } return false; } @@ -1626,12 +1760,13 @@ function initSearch(rawSearchIndex) { * @param {Array<FunctionType>} list * @param {QueryElement} elem - The element from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map<number,number>|null} mgens - Map functions generics to query generics. * * @return {boolean} - Returns true if found, false otherwise. */ - function checkIfInList(list, elem, whereClause) { + function checkIfInList(list, elem, whereClause, mgens) { for (const entry of list) { - if (checkType(entry, elem, whereClause)) { + if (checkType(entry, elem, whereClause, mgens)) { return true; } } @@ -1645,42 +1780,29 @@ function initSearch(rawSearchIndex) { * @param {Row} row * @param {QueryElement} elem - The element from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. + * @param {Map<number,number>|null} mgens - Map functions generics to query generics. * * @return {boolean} - Returns true if the type matches, false otherwise. */ - function checkType(row, elem, whereClause) { - if (row.id === null) { - // This is a pure "generic" search, no need to run other checks. - return row.generics.length > 0 - ? checkIfInList(row.generics, elem, whereClause) - : false; - } - - if (row.id < 0 && elem.id >= 0) { - const gid = (-row.id) - 1; - return checkIfInList(whereClause[gid], elem, whereClause); - } - - if (row.id < 0 && elem.id < 0) { - return true; - } - - const matchesExact = row.id === elem.id; - const matchesArrayOrSlice = elem.id === typeNameIdOfArrayOrSlice && - (row.id === typeNameIdOfSlice || row.id === typeNameIdOfArray); - - if ((matchesExact || matchesArrayOrSlice) && - typePassesFilter(elem.typeFilter, row.ty)) { - if (elem.generics.length > 0) { - return checkGenerics(row, elem, whereClause, new Map()); + function checkType(row, elem, whereClause, mgens) { + if (row.bindings.size === 0 && elem.bindings.size === 0) { + if (elem.id < 0) { + return row.id < 0 || checkIfInList(row.generics, elem, whereClause, mgens); + } + if (row.id > 0 && elem.id > 0 && elem.pathWithoutLast.length === 0 && + typePassesFilter(elem.typeFilter, row.ty) && elem.generics.length === 0 && + // special case + elem.id !== typeNameIdOfArrayOrSlice + ) { + return row.id === elem.id || checkIfInList( + row.generics, + elem, + whereClause, + mgens + ); } - return true; } - - // If the current item does not match, try [unboxing] the generic. - // [unboxing]: - // https://ndmitchell.com/downloads/slides-hoogle_fast_type_searching-09_aug_2008.pdf - return checkIfInList(row.generics, elem, whereClause); + return unifyFunctionTypes([row], [elem], whereClause, mgens); } function checkPath(contains, ty, maxEditDistance) { @@ -1696,26 +1818,16 @@ function initSearch(rawSearchIndex) { const length = path.length; const clength = contains.length; - if (clength > length) { - return maxEditDistance + 1; - } - for (let i = 0; i < length; ++i) { - if (i + clength > length) { - break; - } + pathiter: for (let i = length - clength; i >= 0; i -= 1) { let dist_total = 0; - let aborted = false; for (let x = 0; x < clength; ++x) { const dist = editDistance(path[i + x], contains[x], maxEditDistance); if (dist > maxEditDistance) { - aborted = true; - break; + continue pathiter; } dist_total += dist; } - if (!aborted) { - ret_dist = Math.min(ret_dist, Math.round(dist_total / clength)); - } + ret_dist = Math.min(ret_dist, Math.round(dist_total / clength)); } return ret_dist; } @@ -1819,7 +1931,7 @@ function initSearch(rawSearchIndex) { * The `results` map contains information which will be used to sort the search results: * * * `fullId` is a `string`` used as the key of the object we use for the `results` map. - * * `id` is the index in both `searchWords` and `searchIndex` arrays for this element. + * * `id` is the index in the `searchIndex` array for this element. * * `index` is an `integer`` used to sort by the position of the word in the item's name. * * `dist` is the main metric used to sort the search results. * * `path_dist` is zero if a single-component search query is used, otherwise it's the @@ -1833,8 +1945,7 @@ function initSearch(rawSearchIndex) { * @param {integer} path_dist */ function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) { - const inBounds = dist <= maxEditDistance || index !== -1; - if (dist === 0 || (!parsedQuery.literalSearch && inBounds)) { + if (dist <= maxEditDistance || index !== -1) { if (results.has(fullId)) { const result = results.get(fullId); if (result.dontValidate || result.dist <= dist) { @@ -1878,40 +1989,44 @@ function initSearch(rawSearchIndex) { if (!row || (filterCrates !== null && row.crate !== filterCrates)) { return; } - let index = -1, path_dist = 0; + let path_dist = 0; const fullId = row.id; - const searchWord = searchWords[pos]; - const in_args = row.type && row.type.inputs - && checkIfInList(row.type.inputs, elem, row.type.where_clause); - if (in_args) { - // path_dist is 0 because no parent path information is currently stored - // in the search index - addIntoResults(results_in_args, fullId, pos, -1, 0, 0, maxEditDistance); - } - const returned = row.type && row.type.output - && checkIfInList(row.type.output, elem, row.type.where_clause); - if (returned) { - addIntoResults(results_returned, fullId, pos, -1, 0, 0, maxEditDistance); + // fpDist is a minimum possible type distance, where "type distance" is the number of + // atoms in the function not present in the query + const tfpDist = compareTypeFingerprints( + fullId, + parsedQuery.typeFingerprint + ); + if (tfpDist !== null) { + const in_args = row.type && row.type.inputs + && checkIfInList(row.type.inputs, elem, row.type.where_clause); + const returned = row.type && row.type.output + && checkIfInList(row.type.output, elem, row.type.where_clause); + if (in_args) { + results_in_args.max_dist = Math.max(results_in_args.max_dist || 0, tfpDist); + const maxDist = results_in_args.size < MAX_RESULTS ? + (tfpDist + 1) : + results_in_args.max_dist; + addIntoResults(results_in_args, fullId, pos, -1, tfpDist, 0, maxDist); + } + if (returned) { + results_returned.max_dist = Math.max(results_returned.max_dist || 0, tfpDist); + const maxDist = results_returned.size < MAX_RESULTS ? + (tfpDist + 1) : + results_returned.max_dist; + addIntoResults(results_returned, fullId, pos, -1, tfpDist, 0, maxDist); + } } if (!typePassesFilter(elem.typeFilter, row.ty)) { return; } - const row_index = row.normalizedName.indexOf(elem.pathLast); - const word_index = searchWord.indexOf(elem.pathLast); - - // lower indexes are "better" matches - // rank based on the "best" match - if (row_index === -1) { - index = word_index; - } else if (word_index === -1) { - index = row_index; - } else if (word_index < row_index) { - index = word_index; - } else { - index = row_index; + let index = row.word.indexOf(elem.pathLast); + const normalizedIndex = row.normalizedName.indexOf(elem.pathLast); + if (index === -1 || (index > normalizedIndex && normalizedIndex !== -1)) { + index = normalizedIndex; } if (elem.fullPath.length > 1) { @@ -1922,13 +2037,13 @@ function initSearch(rawSearchIndex) { } if (parsedQuery.literalSearch) { - if (searchWord === elem.name) { + if (row.word === elem.pathLast) { addIntoResults(results_others, fullId, pos, index, 0, path_dist); } return; } - const dist = editDistance(searchWord, elem.pathLast, maxEditDistance); + const dist = editDistance(row.normalizedName, elem.normalizedPathLast, maxEditDistance); if (index === -1 && dist + path_dist > maxEditDistance) { return; @@ -1951,6 +2066,17 @@ function initSearch(rawSearchIndex) { return; } + const tfpDist = compareTypeFingerprints( + row.id, + parsedQuery.typeFingerprint + ); + if (tfpDist === null) { + return; + } + if (results.size >= MAX_RESULTS && tfpDist > results.max_dist) { + return; + } + // If the result is too "bad", we return false and it ends this search. if (!unifyFunctionTypes( row.type.inputs, @@ -1969,12 +2095,11 @@ function initSearch(rawSearchIndex) { return; } - addIntoResults(results, row.id, pos, 0, 0, 0, Number.MAX_VALUE); + results.max_dist = Math.max(results.max_dist || 0, tfpDist); + addIntoResults(results, row.id, pos, 0, tfpDist, 0, Number.MAX_VALUE); } function innerRunQuery() { - let elem, i, nSearchWords, in_returned, row; - let queryLen = 0; for (const elem of parsedQuery.elems) { queryLen += elem.name.length; @@ -2000,17 +2125,20 @@ function initSearch(rawSearchIndex) { * See `buildTypeMapIndex` for more information. * * @param {QueryElement} elem + * @param {boolean} isAssocType */ - function convertNameToId(elem) { - if (typeNameIdMap.has(elem.pathLast)) { - elem.id = typeNameIdMap.get(elem.pathLast); + function convertNameToId(elem, isAssocType) { + if (typeNameIdMap.has(elem.normalizedPathLast) && + (isAssocType || !typeNameIdMap.get(elem.normalizedPathLast).assocOnly)) { + elem.id = typeNameIdMap.get(elem.normalizedPathLast).id; } else if (!parsedQuery.literalSearch) { let match = null; let matchDist = maxEditDistance + 1; let matchName = ""; - for (const [name, id] of typeNameIdMap) { - const dist = editDistance(name, elem.pathLast, maxEditDistance); - if (dist <= matchDist && dist <= maxEditDistance) { + for (const [name, {id, assocOnly}] of typeNameIdMap) { + const dist = editDistance(name, elem.normalizedPathLast, maxEditDistance); + if (dist <= matchDist && dist <= maxEditDistance && + (isAssocType || !assocOnly)) { if (dist === matchDist && matchName > name) { continue; } @@ -2025,7 +2153,7 @@ function initSearch(rawSearchIndex) { elem.id = match; } if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1 - && elem.generics.length === 0) + && elem.generics.length === 0 && elem.bindings.size === 0) || elem.typeFilter === TY_GENERIC) { if (genericSymbols.has(elem.name)) { elem.id = genericSymbols.get(elem.name); @@ -2068,19 +2196,40 @@ function initSearch(rawSearchIndex) { for (const elem2 of elem.generics) { convertNameToId(elem2); } + elem.bindings = new Map(Array.from(elem.bindings.entries()) + .map(entry => { + const [name, constraints] = entry; + if (!typeNameIdMap.has(name)) { + parsedQuery.error = [ + "Type parameter ", + name, + " does not exist", + ]; + return [null, []]; + } + for (const elem2 of constraints) { + convertNameToId(elem2); + } + + return [typeNameIdMap.get(name).id, constraints]; + }) + ); } + const fps = new Set(); for (const elem of parsedQuery.elems) { convertNameToId(elem); + buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps); } for (const elem of parsedQuery.returned) { convertNameToId(elem); + buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint, fps); } - if (parsedQuery.foundElems === 1) { + if (parsedQuery.foundElems === 1 && parsedQuery.returned.length === 0) { if (parsedQuery.elems.length === 1) { - elem = parsedQuery.elems[0]; - for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { + const elem = parsedQuery.elems[0]; + for (let i = 0, nSearchIndex = searchIndex.length; i < nSearchIndex; ++i) { // It means we want to check for this element everywhere (in names, args and // returned). handleSingleArg( @@ -2093,30 +2242,25 @@ function initSearch(rawSearchIndex) { maxEditDistance ); } - } else if (parsedQuery.returned.length === 1) { - // We received one returned argument to check, so looking into returned values. - elem = parsedQuery.returned[0]; - for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { - row = searchIndex[i]; - in_returned = row.type && unifyFunctionTypes( - row.type.output, - parsedQuery.returned, - row.type.where_clause - ); - if (in_returned) { - addIntoResults( - results_others, - row.id, - i, - -1, - 0, - Number.MAX_VALUE - ); - } - } } } else if (parsedQuery.foundElems > 0) { - for (i = 0, nSearchWords = searchWords.length; i < nSearchWords; ++i) { + // Sort input and output so that generic type variables go first and + // types with generic parameters go last. + // That's because of the way unification is structured: it eats off + // the end, and hits a fast path if the last item is a simple atom. + const sortQ = (a, b) => { + const ag = a.generics.length === 0 && a.bindings.size === 0; + const bg = b.generics.length === 0 && b.bindings.size === 0; + if (ag !== bg) { + return ag - bg; + } + const ai = a.id > 0; + const bi = b.id > 0; + return ai - bi; + }; + parsedQuery.elems.sort(sortQ); + parsedQuery.returned.sort(sortQ); + for (let i = 0, nSearchIndex = searchIndex.length; i < nSearchIndex; ++i) { handleArgs(searchIndex[i], i, results_others); } } @@ -2139,44 +2283,6 @@ function initSearch(rawSearchIndex) { return ret; } - /** - * Validate performs the following boolean logic. For example: - * "File::open" will give IF A PARENT EXISTS => ("file" && "open") - * exists in (name || path || parent) OR => ("file" && "open") exists in - * (name || path ) - * - * This could be written functionally, but I wanted to minimise - * functions on stack. - * - * @param {string} name - The name of the result - * @param {string} path - The path of the result - * @param {string} keys - The keys to be used (["file", "open"]) - * @param {Object} parent - The parent of the result - * - * @return {boolean} - Whether the result is valid or not - */ - function validateResult(name, path, keys, parent, maxEditDistance) { - if (!keys || !keys.length) { - return true; - } - for (const key of keys) { - // each check is for validation so we negate the conditions and invalidate - if (!( - // check for an exact name match - name.indexOf(key) > -1 || - // then an exact path match - path.indexOf(key) > -1 || - // next if there is a parent, check for exact parent match - (parent !== undefined && parent.name !== undefined && - parent.name.toLowerCase().indexOf(key) > -1) || - // lastly check to see if the name was an editDistance match - editDistance(name, key, maxEditDistance) <= maxEditDistance)) { - return false; - } - } - return true; - } - function nextTab(direction) { const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length; searchState.focusedByTab[searchState.currentTab] = document.activeElement; @@ -2269,13 +2375,9 @@ function initSearch(rawSearchIndex) { * @param {boolean} display - True if this is the active tab */ function addTab(array, query, display) { - let extraClass = ""; - if (display === true) { - extraClass = " active"; - } + const extraClass = display ? " active" : ""; const output = document.createElement("div"); - let length = 0; if (array.length > 0) { output.className = "search-results " + extraClass; @@ -2285,8 +2387,6 @@ function initSearch(rawSearchIndex) { const longType = longItemTypes[item.ty]; const typeName = longType.length !== 0 ? `${longType}` : "?"; - length += 1; - const link = document.createElement("a"); link.className = "result-" + type; link.href = item.href; @@ -2334,7 +2434,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\ "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" + " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>"; } - return [output, length]; + return [output, array.length]; } function makeTabHeader(tabNb, text, nbElems) { @@ -2413,11 +2513,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\ } let crates = ""; - const crates_list = Object.keys(rawSearchIndex); - if (crates_list.length > 1) { + if (rawSearchIndex.size > 1) { crates = " in <div id=\"crate-search-div\"><select id=\"crate-search\">" + "<option value=\"all crates\">all crates</option>"; - for (const c of crates_list) { + for (const c of rawSearchIndex.keys()) { crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`; } crates += "</select></div>"; @@ -2514,13 +2613,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\ /** * Perform a search based on the current state of the search input element * and display the results. - * @param {Event} [e] - The event that triggered this search, if any * @param {boolean} [forced] */ - function search(e, forced) { - if (e) { - e.preventDefault(); - } + function search(forced) { const query = parseQuery(searchState.input.value.trim()); let filterCrates = getFilterCrates(); @@ -2549,7 +2644,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\ updateSearchHistory(buildUrl(query.original, filterCrates)); showResults( - execQuery(query, searchWords, filterCrates, window.currentCrate), + execQuery(query, filterCrates, window.currentCrate), params.go_to_first, filterCrates); } @@ -2581,19 +2676,42 @@ ${item.displayPath}<span class="${type}">${name}</span>\ * * @param {RawFunctionType} type */ - function buildItemSearchType(type, lowercasePaths) { + function buildItemSearchType(type, lowercasePaths, isAssocType) { const PATH_INDEX_DATA = 0; const GENERICS_DATA = 1; - let pathIndex, generics; + const BINDINGS_DATA = 2; + let pathIndex, generics, bindings; if (typeof type === "number") { pathIndex = type; generics = []; + bindings = new Map(); } else { pathIndex = type[PATH_INDEX_DATA]; generics = buildItemSearchTypeAll( type[GENERICS_DATA], lowercasePaths ); + if (type.length > BINDINGS_DATA) { + bindings = new Map(type[BINDINGS_DATA].map(binding => { + const [assocType, constraints] = binding; + // Associated type constructors are represented sloppily in rustdoc's + // type search, to make the engine simpler. + // + // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T> + // and both are, essentially + // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there. + // It's more like the value of a type binding is naturally an array, + // which rustdoc calls "constraints". + // + // As a result, the key should never have generics on it. + return [ + buildItemSearchType(assocType, lowercasePaths, true).id, + buildItemSearchTypeAll(constraints, lowercasePaths), + ]; + })); + } else { + bindings = new Map(); + } } if (pathIndex < 0) { // types less than 0 are generic parameters @@ -2603,6 +2721,7 @@ ${item.displayPath}<span class="${type}">${name}</span>\ ty: TY_GENERIC, path: null, generics, + bindings, }; } if (pathIndex === 0) { @@ -2612,14 +2731,16 @@ ${item.displayPath}<span class="${type}">${name}</span>\ ty: null, path: null, generics, + bindings, }; } const item = lowercasePaths[pathIndex - 1]; return { - id: buildTypeMapIndex(item.name), + id: buildTypeMapIndex(item.name, isAssocType), ty: item.ty, path: item.path, generics, + bindings, }; } @@ -2679,14 +2800,119 @@ ${item.displayPath}<span class="${type}">${name}</span>\ }; } + /** + * Type fingerprints allow fast, approximate matching of types. + * + * This algo creates a compact representation of the type set using a Bloom filter. + * This fingerprint is used three ways: + * + * - It accelerates the matching algorithm by checking the function fingerprint against the + * query fingerprint. If any bits are set in the query but not in the function, it can't + * match. + * + * - The fourth section has the number of distinct items in the set. + * This is the distance function, used for filtering and for sorting. + * + * [^1]: Distance is the relatively naive metric of counting the number of distinct items in + * the function that are not present in the query. + * + * @param {FunctionType|QueryElement} type - a single type + * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits + * @param {Set<number>} fps - Set of distinct items + */ + function buildFunctionTypeFingerprint(type, output, fps) { + let input = type.id; + // All forms of `[]` get collapsed down to one thing in the bloom filter. + // Differentiating between arrays and slices, if the user asks for it, is + // still done in the matching algorithm. + if (input === typeNameIdOfArray || input === typeNameIdOfSlice) { + input = typeNameIdOfArrayOrSlice; + } + // http://burtleburtle.net/bob/hash/integer.html + // ~~ is toInt32. It's used before adding, so + // the number stays in safe integer range. + const hashint1 = k => { + k = (~~k + 0x7ed55d16) + (k << 12); + k = (k ^ 0xc761c23c) ^ (k >>> 19); + k = (~~k + 0x165667b1) + (k << 5); + k = (~~k + 0xd3a2646c) ^ (k << 9); + k = (~~k + 0xfd7046c5) + (k << 3); + return (k ^ 0xb55a4f09) ^ (k >>> 16); + }; + const hashint2 = k => { + k = ~k + (k << 15); + k ^= k >>> 12; + k += k << 2; + k ^= k >>> 4; + k = Math.imul(k, 2057); + return k ^ (k >> 16); + }; + if (input !== null) { + const h0a = hashint1(input); + const h0b = hashint2(input); + // Less Hashing, Same Performance: Building a Better Bloom Filter + // doi=10.1.1.72.2442 + const h1a = ~~(h0a + Math.imul(h0b, 2)); + const h1b = ~~(h0a + Math.imul(h0b, 3)); + const h2a = ~~(h0a + Math.imul(h0b, 4)); + const h2b = ~~(h0a + Math.imul(h0b, 5)); + output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32)); + output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32)); + output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32)); + fps.add(input); + } + for (const g of type.generics) { + buildFunctionTypeFingerprint(g, output, fps); + } + const fb = { + id: null, + ty: 0, + generics: [], + bindings: new Map(), + }; + for (const [k, v] of type.bindings.entries()) { + fb.id = k; + fb.generics = v; + buildFunctionTypeFingerprint(fb, output, fps); + } + output[3] = fps.size; + } + + /** + * Compare the query fingerprint with the function fingerprint. + * + * @param {{number}} fullId - The function + * @param {{Uint32Array}} queryFingerprint - The query + * @returns {number|null} - Null if non-match, number if distance + * This function might return 0! + */ + function compareTypeFingerprints(fullId, queryFingerprint) { + const fh0 = functionTypeFingerprint[fullId * 4]; + const fh1 = functionTypeFingerprint[(fullId * 4) + 1]; + const fh2 = functionTypeFingerprint[(fullId * 4) + 2]; + const [qh0, qh1, qh2] = queryFingerprint; + // Approximate set intersection with bloom filters. + // This can be larger than reality, not smaller, because hashes have + // the property that if they've got the same value, they hash to the + // same thing. False positives exist, but not false negatives. + const [in0, in1, in2] = [fh0 & qh0, fh1 & qh1, fh2 & qh2]; + // Approximate the set of items in the query but not the function. + // This might be smaller than reality, but cannot be bigger. + // + // | in_ | qh_ | XOR | Meaning | + // | --- | --- | --- | ------------------------------------------------ | + // | 0 | 0 | 0 | Not present | + // | 1 | 0 | 1 | IMPOSSIBLE because `in_` is `fh_ & qh_` | + // | 1 | 1 | 0 | If one or both is false positive, false negative | + // | 0 | 1 | 1 | Since in_ has no false negatives, must be real | + if ((in0 ^ qh0) || (in1 ^ qh1) || (in2 ^ qh2)) { + return null; + } + return functionTypeFingerprint[(fullId * 4) + 3]; + } + function buildIndex(rawSearchIndex) { searchIndex = []; - /** - * List of normalized search words (ASCII lowercased, and undescores removed). - * - * @type {Array<string>} - */ - const searchWords = []; typeNameIdMap = new Map(); const charA = "A".charCodeAt(0); let currentIndex = 0; @@ -2698,81 +2924,86 @@ ${item.displayPath}<span class="${type}">${name}</span>\ typeNameIdOfSlice = buildTypeMapIndex("slice"); typeNameIdOfArrayOrSlice = buildTypeMapIndex("[]"); - for (const crate in rawSearchIndex) { - if (!hasOwnPropertyRustdoc(rawSearchIndex, crate)) { - continue; - } - - let crateSize = 0; - - /** - * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f` - * are arrays with the same length. `q`, `a`, and `c` use a sparse - * representation for compactness. - * - * `n[i]` contains the name of an item. - * - * `t[i]` contains the type of that item - * (as a string of characters that represent an offset in `itemTypes`). - * - * `d[i]` contains the description of that item. - * - * `q` contains the full paths of the items. For compactness, it is a set of - * (index, path) pairs used to create a map. If a given index `i` is - * not present, this indicates "same as the last index present". - * - * `i[i]` contains an item's parent, usually a module. For compactness, - * it is a set of indexes into the `p` array. - * - * `f[i]` contains function signatures, or `0` if the item isn't a function. - * Functions are themselves encoded as arrays. The first item is a list of - * types representing the function's inputs, and the second list item is a list - * of types representing the function's output. Tuples are flattened. - * Types are also represented as arrays; the first item is an index into the `p` - * array, while the second is a list of types representing any generic parameters. - * - * b[i] contains an item's impl disambiguator. This is only present if an item - * is defined in an impl block and, the impl block's type has more than one associated - * item with the same name. - * - * `a` defines aliases with an Array of pairs: [name, offset], where `offset` - * points into the n/t/d/q/i/f arrays. - * - * `doc` contains the description of the crate. - * - * `p` is a list of path/type pairs. It is used for parents and function parameters. - * - * `c` is an array of item indices that are deprecated. - * - * @type {{ - * doc: string, - * a: Object, - * n: Array<string>, - * t: String, - * d: Array<string>, - * q: Array<[Number, string]>, - * i: Array<Number>, - * f: Array<RawFunctionSearchType>, - * p: Array<Object>, - * b: Array<[Number, String]>, - * c: Array<Number> - * }} - */ - const crateCorpus = rawSearchIndex[crate]; + // Function type fingerprints are 128-bit bloom filters that are used to + // estimate the distance between function and query. + // This loop counts the number of items to allocate a fingerprint for. + for (const crate of rawSearchIndex.values()) { + // Each item gets an entry in the fingerprint array, and the crate + // does, too + id += crate.t.length + 1; + } + functionTypeFingerprint = new Uint32Array((id + 1) * 4); - searchWords.push(crate); + // This loop actually generates the search item indexes, including + // normalized names, type signature objects and fingerprints, and aliases. + id = 0; + /** + * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f` + * are arrays with the same length. `q`, `a`, and `c` use a sparse + * representation for compactness. + * + * `n[i]` contains the name of an item. + * + * `t[i]` contains the type of that item + * (as a string of characters that represent an offset in `itemTypes`). + * + * `d[i]` contains the description of that item. + * + * `q` contains the full paths of the items. For compactness, it is a set of + * (index, path) pairs used to create a map. If a given index `i` is + * not present, this indicates "same as the last index present". + * + * `i[i]` contains an item's parent, usually a module. For compactness, + * it is a set of indexes into the `p` array. + * + * `f[i]` contains function signatures, or `0` if the item isn't a function. + * Functions are themselves encoded as arrays. The first item is a list of + * types representing the function's inputs, and the second list item is a list + * of types representing the function's output. Tuples are flattened. + * Types are also represented as arrays; the first item is an index into the `p` + * array, while the second is a list of types representing any generic parameters. + * + * b[i] contains an item's impl disambiguator. This is only present if an item + * is defined in an impl block and, the impl block's type has more than one associated + * item with the same name. + * + * `a` defines aliases with an Array of pairs: [name, offset], where `offset` + * points into the n/t/d/q/i/f arrays. + * + * `doc` contains the description of the crate. + * + * `p` is a list of path/type pairs. It is used for parents and function parameters. + * + * `c` is an array of item indices that are deprecated. + * + * @type {{ + * doc: string, + * a: Object, + * n: Array<string>, + * t: String, + * d: Array<string>, + * q: Array<[Number, string]>, + * i: Array<Number>, + * f: Array<RawFunctionSearchType>, + * p: Array<Object>, + * b: Array<[Number, String]>, + * c: Array<Number> + * }} + */ + for (const [crate, crateCorpus] of rawSearchIndex) { // This object should have exactly the same set of fields as the "row" // object defined below. Your JavaScript runtime will thank you. // https://mathiasbynens.be/notes/shapes-ics const crateRow = { crate: crate, - ty: 1, // == ExternCrate + ty: 3, // == ExternCrate name: crate, path: "", desc: crateCorpus.doc, parent: undefined, type: null, id: id, + word: crate, normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), deprecated: null, implDisambiguator: null, @@ -2840,13 +3071,34 @@ ${item.displayPath}<span class="${type}">${name}</span>\ len = itemTypes.length; for (let i = 0; i < len; ++i) { let word = ""; - // This object should have exactly the same set of fields as the "crateRow" - // object defined above. if (typeof itemNames[i] === "string") { word = itemNames[i].toLowerCase(); } - searchWords.push(word); const path = itemPaths.has(i) ? itemPaths.get(i) : lastPath; + let type = null; + if (itemFunctionSearchTypes[i] !== 0) { + type = buildFunctionSearchType( + itemFunctionSearchTypes[i], + lowercasePaths + ); + if (type) { + const fp = functionTypeFingerprint.subarray(id * 4, (id + 1) * 4); + const fps = new Set(); + for (const t of type.inputs) { + buildFunctionTypeFingerprint(t, fp, fps); + } + for (const t of type.output) { + buildFunctionTypeFingerprint(t, fp, fps); + } + for (const w of type.where_clause) { + for (const t of w) { + buildFunctionTypeFingerprint(t, fp, fps); + } + } + } + } + // This object should have exactly the same set of fields as the "crateRow" + // object defined above. const row = { crate: crate, ty: itemTypes.charCodeAt(i) - charA, @@ -2854,11 +3106,9 @@ ${item.displayPath}<span class="${type}">${name}</span>\ path: path, desc: itemDescs[i], parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, - type: buildFunctionSearchType( - itemFunctionSearchTypes[i], - lowercasePaths - ), + type, id: id, + word, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), deprecated: deprecatedItems.has(i), implDisambiguator: implDisambiguator.has(i) ? implDisambiguator.get(i) : null, @@ -2866,14 +3116,13 @@ ${item.displayPath}<span class="${type}">${name}</span>\ id += 1; searchIndex.push(row); lastPath = row.path; - crateSize += 1; } if (aliases) { const currentCrateAliases = new Map(); ALIASES.set(crate, currentCrateAliases); for (const alias_name in aliases) { - if (!hasOwnPropertyRustdoc(aliases, alias_name)) { + if (!Object.prototype.hasOwnProperty.call(aliases, alias_name)) { continue; } @@ -2889,9 +3138,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\ } } } - currentIndex += crateSize; + currentIndex += itemTypes.length; } - return searchWords; } /** @@ -3031,7 +3279,8 @@ ${item.displayPath}<span class="${type}">${name}</span>\ // popping a state (Firefox), which is why search() is // called both here and at the end of the startSearch() // function. - search(e); + e.preventDefault(); + search(); } else { searchState.input.value = ""; // When browsing back from search results the main page @@ -3066,13 +3315,10 @@ ${item.displayPath}<span class="${type}">${name}</span>\ // before paste back the previous search, you get the old search results without // the filter. To prevent this, we need to remove the previous results. currentResults = null; - search(undefined, true); + search(true); } - /** - * @type {Array<string>} - */ - const searchWords = buildIndex(rawSearchIndex); + buildIndex(rawSearchIndex); if (typeof window !== "undefined") { registerSearchEvents(); // If there's a search term in the URL, execute the search now. @@ -3086,7 +3332,6 @@ ${item.displayPath}<span class="${type}">${name}</span>\ exports.execQuery = execQuery; exports.parseQuery = parseQuery; } - return searchWords; } if (typeof window !== "undefined") { @@ -3097,7 +3342,7 @@ if (typeof window !== "undefined") { } else { // Running in Node, not a browser. Run initSearch just to produce the // exports. - initSearch({}); + initSearch(new Map()); } diff --git a/src/librustdoc/html/static/js/settings.js b/src/librustdoc/html/static/js/settings.js index 63947789c..2b42fbebb 100644 --- a/src/librustdoc/html/static/js/settings.js +++ b/src/librustdoc/html/static/js/settings.js @@ -1,6 +1,6 @@ // Local js definitions: /* global getSettingValue, updateLocalStorage, updateTheme */ -/* global addClass, removeClass, onEach, onEachLazy, blurHandler, elemIsInParent */ +/* global addClass, removeClass, onEach, onEachLazy, blurHandler */ /* global MAIN_ID, getVar, getSettingsButton */ "use strict"; @@ -29,6 +29,13 @@ window.rustdoc_remove_line_numbers_from_examples(); } break; + case "hide-sidebar": + if (value === true) { + addClass(document.documentElement, "hide-sidebar"); + } else { + removeClass(document.documentElement, "hide-sidebar"); + } + break; } } @@ -187,6 +194,11 @@ "default": false, }, { + "name": "Hide persistent navigation bar", + "js_name": "hide-sidebar", + "default": false, + }, + { "name": "Disable keyboard shortcuts", "js_name": "disable-shortcuts", "default": false, @@ -216,6 +228,13 @@ function displaySettings() { settingsMenu.style.display = ""; + onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => { + const val = getSettingValue(el.id); + const checked = val === "true"; + if (checked !== el.checked && val !== null) { + el.checked = checked; + } + }); } function settingsBlurHandler(event) { @@ -232,7 +251,7 @@ const settingsButton = getSettingsButton(); const settingsMenu = document.getElementById("settings"); settingsButton.onclick = event => { - if (elemIsInParent(event.target, settingsMenu)) { + if (settingsMenu.contains(event.target)) { return; } event.preventDefault(); diff --git a/src/librustdoc/html/static/js/src-script.js b/src/librustdoc/html/static/js/src-script.js index 679c2341f..fc1d2d378 100644 --- a/src/librustdoc/html/static/js/src-script.js +++ b/src/librustdoc/html/static/js/src-script.js @@ -71,16 +71,31 @@ function createDirEntry(elem, parent, fullPath, hasFoundFile) { return hasFoundFile; } +let toggleLabel; + +function getToggleLabel() { + toggleLabel = toggleLabel || document.querySelector("#src-sidebar-toggle button"); + return toggleLabel; +} + +window.rustdocCloseSourceSidebar = () => { + removeClass(document.documentElement, "src-sidebar-expanded"); + getToggleLabel().innerText = ">"; + updateLocalStorage("source-sidebar-show", "false"); +}; + +window.rustdocShowSourceSidebar = () => { + addClass(document.documentElement, "src-sidebar-expanded"); + getToggleLabel().innerText = "<"; + updateLocalStorage("source-sidebar-show", "true"); +}; + function toggleSidebar() { const child = this.parentNode.children[0]; if (child.innerText === ">") { - addClass(document.documentElement, "src-sidebar-expanded"); - child.innerText = "<"; - updateLocalStorage("source-sidebar-show", "true"); + window.rustdocShowSourceSidebar(); } else { - removeClass(document.documentElement, "src-sidebar-expanded"); - child.innerText = ">"; - updateLocalStorage("source-sidebar-show", "false"); + window.rustdocCloseSourceSidebar(); } } @@ -118,10 +133,10 @@ function createSrcSidebar() { title.className = "title"; title.innerText = "Files"; sidebar.appendChild(title); - Object.keys(srcIndex).forEach(key => { - srcIndex[key][NAME_OFFSET] = key; - hasFoundFile = createDirEntry(srcIndex[key], sidebar, "", hasFoundFile); - }); + for (const [key, source] of srcIndex) { + source[NAME_OFFSET] = key; + hasFoundFile = createDirEntry(source, sidebar, "", hasFoundFile); + } container.appendChild(sidebar); // Focus on the current file in the source files sidebar. @@ -131,12 +146,8 @@ function createSrcSidebar() { } } -const lineNumbersRegex = /^#?(\d+)(?:-(\d+))?$/; - -function highlightSrcLines(match) { - if (typeof match === "undefined") { - match = window.location.hash.match(lineNumbersRegex); - } +function highlightSrcLines() { + const match = window.location.hash.match(/^#?(\d+)(?:-(\d+))?$/); if (!match) { return; } @@ -218,12 +229,7 @@ const handleSrcHighlight = (function() { }; }()); -window.addEventListener("hashchange", () => { - const match = window.location.hash.match(lineNumbersRegex); - if (match) { - return highlightSrcLines(match); - } -}); +window.addEventListener("hashchange", highlightSrcLines); onEachLazy(document.getElementsByClassName("src-line-numbers"), el => { el.addEventListener("click", handleSrcHighlight); diff --git a/src/librustdoc/html/static/js/storage.js b/src/librustdoc/html/static/js/storage.js index c69641092..ac9c6f377 100644 --- a/src/librustdoc/html/static/js/storage.js +++ b/src/librustdoc/html/static/js/storage.js @@ -51,22 +51,11 @@ function removeClass(elem, className) { * Run a callback for every element of an Array. * @param {Array<?>} arr - The array to iterate over * @param {function(?)} func - The callback - * @param {boolean} [reversed] - Whether to iterate in reverse */ -function onEach(arr, func, reversed) { - if (arr && arr.length > 0) { - if (reversed) { - for (let i = arr.length - 1; i >= 0; --i) { - if (func(arr[i])) { - return true; - } - } - } else { - for (const elem of arr) { - if (func(elem)) { - return true; - } - } +function onEach(arr, func) { + for (const elem of arr) { + if (func(elem)) { + return true; } } return false; @@ -80,14 +69,12 @@ function onEach(arr, func, reversed) { * https://developer.mozilla.org/en-US/docs/Web/API/NodeList * @param {NodeList<?>|HTMLCollection<?>} lazyArray - An array to iterate over * @param {function(?)} func - The callback - * @param {boolean} [reversed] - Whether to iterate in reverse */ // eslint-disable-next-line no-unused-vars -function onEachLazy(lazyArray, func, reversed) { +function onEachLazy(lazyArray, func) { return onEach( Array.prototype.slice.call(lazyArray), - func, - reversed); + func); } function updateLocalStorage(name, value) { @@ -196,11 +183,38 @@ if (getSettingValue("use-system-theme") !== "false" && window.matchMedia) { updateTheme(); +// Hide, show, and resize the sidebar at page load time +// +// This needs to be done here because this JS is render-blocking, +// so that the sidebar doesn't "jump" after appearing on screen. +// The user interaction to change this is set up in main.js. if (getSettingValue("source-sidebar-show") === "true") { // At this point in page load, `document.body` is not available yet. // Set a class on the `<html>` element instead. addClass(document.documentElement, "src-sidebar-expanded"); } +if (getSettingValue("hide-sidebar") === "true") { + // At this point in page load, `document.body` is not available yet. + // Set a class on the `<html>` element instead. + addClass(document.documentElement, "hide-sidebar"); +} +function updateSidebarWidth() { + const desktopSidebarWidth = getSettingValue("desktop-sidebar-width"); + if (desktopSidebarWidth && desktopSidebarWidth !== "null") { + document.documentElement.style.setProperty( + "--desktop-sidebar-width", + desktopSidebarWidth + "px" + ); + } + const srcSidebarWidth = getSettingValue("src-sidebar-width"); + if (srcSidebarWidth && srcSidebarWidth !== "null") { + document.documentElement.style.setProperty( + "--src-sidebar-width", + srcSidebarWidth + "px" + ); + } +} +updateSidebarWidth(); // If we navigate away (for example to a settings page), and then use the back or // forward button to get back to a page, the theme may have changed in the meantime. @@ -214,5 +228,6 @@ if (getSettingValue("source-sidebar-show") === "true") { window.addEventListener("pageshow", ev => { if (ev.persisted) { setTimeout(updateTheme, 0); + setTimeout(updateSidebarWidth, 0); } }); diff --git a/src/librustdoc/html/templates/item_union.html b/src/librustdoc/html/templates/item_union.html index f6d2fa348..8db7986fa 100644 --- a/src/librustdoc/html/templates/item_union.html +++ b/src/librustdoc/html/templates/item_union.html @@ -4,13 +4,13 @@ </code></pre> {{ self.document() | safe }} {% if self.fields_iter().peek().is_some() %} - <h2 id="fields" class="fields small-section-header"> {# #} + <h2 id="fields" class="fields section-header"> {# #} Fields<a href="#fields" class="anchor">§</a> {# #} </h2> {% for (field, ty) in self.fields_iter() %} {% let name = field.name.expect("union field name") %} <span id="structfield.{{ name }}" {#+ #} - class="{{ ItemType::StructField +}} small-section-header"> {# #} + class="{{ ItemType::StructField +}} section-header"> {# #} <a href="#structfield.{{ name }}" class="anchor field">§</a> {# #} <code>{{ name }}: {{+ self.print_ty(ty) | safe }}</code> {# #} </span> diff --git a/src/librustdoc/html/templates/page.html b/src/librustdoc/html/templates/page.html index 3f6147bb9..60ca5660c 100644 --- a/src/librustdoc/html/templates/page.html +++ b/src/librustdoc/html/templates/page.html @@ -114,6 +114,7 @@ {% endif %} {{ sidebar|safe }} </nav> {# #} + <div class="sidebar-resizer"></div> <main> {# #} {% if page.css_class != "src" %}<div class="width-limiter">{% endif %} <nav class="sub"> {# #} @@ -128,6 +129,11 @@ {% endif %} <form class="search-form"> {# #} <span></span> {# This empty span is a hacky fix for Safari - See #93184 #} + {% if page.css_class != "src" %} + <div id="sidebar-button" tabindex="-1"> {# #} + <a href="{{page.root_path|safe}}{{layout.krate|safe}}/all.html" title="show sidebar"></a> {# #} + </div> {# #} + {% endif %} <input {#+ #} class="search-input" {#+ #} name="search" {#+ #} @@ -136,8 +142,8 @@ spellcheck="false" {#+ #} placeholder="Click or press ‘S’ to search, ‘?’ for more options…" {#+ #} type="search"> {# #} - <div id="help-button" title="help" tabindex="-1"> {# #} - <a href="{{page.root_path|safe}}help.html">?</a> {# #} + <div id="help-button" tabindex="-1"> {# #} + <a href="{{page.root_path|safe}}help.html" title="help">?</a> {# #} </div> {# #} <div id="settings-menu" tabindex="-1"> {# #} <a href="{{page.root_path|safe}}settings.html" title="settings"> {# #} diff --git a/src/librustdoc/html/templates/type_layout.html b/src/librustdoc/html/templates/type_layout.html index b8b7785a2..e0516bb42 100644 --- a/src/librustdoc/html/templates/type_layout.html +++ b/src/librustdoc/html/templates/type_layout.html @@ -1,4 +1,4 @@ -<h2 id="layout" class="small-section-header"> {# #} +<h2 id="layout" class="section-header"> {# #} Layout<a href="#layout" class="anchor">§</a> {# #} </h2> {# #} <div class="docblock"> {# #} @@ -54,7 +54,7 @@ <strong>Note:</strong> Encountered an error during type layout; {#+ #} the type failed to be normalized. {# #} </p> {# #} - {% when Err(LayoutError::Cycle) %} + {% when Err(LayoutError::Cycle(_)) %} <p> {# #} <strong>Note:</strong> Encountered an error during type layout; {#+ #} the type's layout depended on the type's layout itself. {# #} diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 285923251..61376ab31 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -250,15 +250,16 @@ pub(crate) fn id_from_item_inner( // their parent module, which isn't present in the output JSON items. So // instead, we directly get the primitive symbol and convert it to u32 to // generate the ID. - if matches!(tcx.def_kind(def_id), DefKind::Mod) && - let Some(prim) = tcx.get_attrs(*def_id, sym::rustc_doc_primitive) - .find_map(|attr| attr.value_str()) { + if matches!(tcx.def_kind(def_id), DefKind::Mod) + && let Some(prim) = tcx + .get_attrs(*def_id, sym::rustc_doc_primitive) + .find_map(|attr| attr.value_str()) + { format!(":{}", prim.as_u32()) } else { - tcx - .opt_item_name(*def_id) - .map(|n| format!(":{}", n.as_u32())) - .unwrap_or_default() + tcx.opt_item_name(*def_id) + .map(|n| format!(":{}", n.as_u32())) + .unwrap_or_default() } } }; diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 27e8a27ba..c1d90020e 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -80,9 +80,9 @@ impl<'tcx> JsonRenderer<'tcx> { // document primitive items in an arbitrary crate by using // `rustc_doc_primitive`. let mut is_primitive_impl = false; - if let clean::types::ItemKind::ImplItem(ref impl_) = *item.kind && - impl_.trait_.is_none() && - let clean::types::Type::Primitive(_) = impl_.for_ + if let clean::types::ItemKind::ImplItem(ref impl_) = *item.kind + && impl_.trait_.is_none() + && let clean::types::Type::Primitive(_) = impl_.for_ { is_primitive_impl = true; } diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs index dda06d4c9..52803c2b0 100644 --- a/src/librustdoc/lib.rs +++ b/src/librustdoc/lib.rs @@ -53,6 +53,7 @@ extern crate rustc_interface; extern crate rustc_lexer; extern crate rustc_lint; extern crate rustc_lint_defs; +extern crate rustc_log; extern crate rustc_macros; extern crate rustc_metadata; extern crate rustc_middle; @@ -81,7 +82,7 @@ use rustc_errors::ErrorGuaranteed; use rustc_interface::interface; use rustc_middle::ty::TyCtxt; use rustc_session::config::{make_crate_type_option, ErrorOutputType, RustcOptGroup}; -use rustc_session::{getopts, EarlyErrorHandler}; +use rustc_session::{getopts, EarlyDiagCtxt}; use crate::clean::utils::DOC_RUST_LANG_ORG_CHANNEL; @@ -156,7 +157,7 @@ pub fn main() { } } - let mut handler = EarlyErrorHandler::new(ErrorOutputType::default()); + let mut early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); let using_internal_features = rustc_driver::install_ice_hook( "https://github.com/rust-lang/rust/issues/new\ @@ -174,11 +175,11 @@ pub fn main() { // `debug_logging = true` is because all rustc logging goes to its version of tracing (the one // in the sysroot), and all of rustdoc's logging goes to its version (the one in Cargo.toml). - init_logging(&handler); - rustc_driver::init_env_logger(&handler, "RUSTDOC_LOG"); + init_logging(&early_dcx); + rustc_driver::init_logger(&early_dcx, rustc_log::LoggerConfig::from_env("RUSTDOC_LOG")); - let exit_code = rustc_driver::catch_with_exit_code(|| match get_args(&handler) { - Some(args) => main_args(&mut handler, &args, using_internal_features), + let exit_code = rustc_driver::catch_with_exit_code(|| match get_args(&early_dcx) { + Some(args) => main_args(&mut early_dcx, &args, using_internal_features), _ => { #[allow(deprecated)] @@ -188,15 +189,15 @@ pub fn main() { process::exit(exit_code); } -fn init_logging(handler: &EarlyErrorHandler) { +fn init_logging(early_dcx: &EarlyDiagCtxt) { let color_logs = match std::env::var("RUSTDOC_LOG_COLOR").as_deref() { Ok("always") => true, Ok("never") => false, Ok("auto") | Err(VarError::NotPresent) => io::stdout().is_terminal(), - Ok(value) => handler.early_error(format!( + Ok(value) => early_dcx.early_error(format!( "invalid log color value '{value}': expected one of always, never, or auto", )), - Err(VarError::NotUnicode(value)) => handler.early_error(format!( + Err(VarError::NotUnicode(value)) => early_dcx.early_error(format!( "invalid log color value '{}': expected one of always, never, or auto", value.to_string_lossy() )), @@ -219,13 +220,13 @@ fn init_logging(handler: &EarlyErrorHandler) { tracing::subscriber::set_global_default(subscriber).unwrap(); } -fn get_args(handler: &EarlyErrorHandler) -> Option<Vec<String>> { +fn get_args(early_dcx: &EarlyDiagCtxt) -> Option<Vec<String>> { env::args_os() .enumerate() .map(|(i, arg)| { arg.into_string() .map_err(|arg| { - handler.early_warn(format!("Argument {i} is not valid Unicode: {arg:?}")); + early_dcx.early_warn(format!("Argument {i} is not valid Unicode: {arg:?}")); }) .ok() }) @@ -672,11 +673,11 @@ fn usage(argv0: &str) { /// A result type used by several functions under `main()`. type MainResult = Result<(), ErrorGuaranteed>; -fn wrap_return(diag: &rustc_errors::Handler, res: Result<(), String>) -> MainResult { +fn wrap_return(dcx: &rustc_errors::DiagCtxt, res: Result<(), String>) -> MainResult { match res { - Ok(()) => diag.has_errors().map_or(Ok(()), Err), + Ok(()) => dcx.has_errors().map_or(Ok(()), Err), Err(err) => { - let reported = diag.struct_err(err).emit(); + let reported = dcx.struct_err(err).emit(); Err(reported) } } @@ -703,7 +704,7 @@ fn run_renderer<'tcx, T: formats::FormatRenderer<'tcx>>( } fn main_args( - handler: &mut EarlyErrorHandler, + early_dcx: &mut EarlyDiagCtxt, at_args: &[String], using_internal_features: Arc<AtomicBool>, ) -> MainResult { @@ -717,7 +718,7 @@ fn main_args( // the compiler with @empty_file as argv[0] and no more arguments. let at_args = at_args.get(1..).unwrap_or_default(); - let args = rustc_driver::args::arg_expand_all(handler, at_args); + let args = rustc_driver::args::arg_expand_all(early_dcx, at_args); let mut options = getopts::Options::new(); for option in opts() { @@ -726,13 +727,13 @@ fn main_args( let matches = match options.parse(&args) { Ok(m) => m, Err(err) => { - handler.early_error(err.to_string()); + early_dcx.early_error(err.to_string()); } }; // Note that we discard any distinction between different non-zero exit // codes from `from_matches` here. - let (options, render_options) = match config::Options::from_matches(handler, &matches, args) { + let (options, render_options) = match config::Options::from_matches(early_dcx, &matches, args) { Ok(opts) => opts, Err(code) => { return if code == 0 { @@ -744,12 +745,8 @@ fn main_args( } }; - let diag = core::new_handler( - options.error_format, - None, - options.diagnostic_width, - &options.unstable_opts, - ); + let diag = + core::new_dcx(options.error_format, None, options.diagnostic_width, &options.unstable_opts); match (options.should_test, options.markdown_input()) { (true, true) => return wrap_return(&diag, markdown::test(options)), @@ -773,7 +770,7 @@ fn main_args( } // need to move these items separately because we lose them by the time the closure is called, - // but we can't create the Handler ahead of time because it's not Send + // but we can't create the dcx ahead of time because it's not Send let show_coverage = options.show_coverage; let run_check = options.run_check; @@ -793,23 +790,16 @@ fn main_args( let config = core::create_config(options, &render_options, using_internal_features); interface::run_compiler(config, |compiler| { - let sess = compiler.session(); + let sess = &compiler.sess; if sess.opts.describe_lints { - let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints()); - let registered_lints = if let Some(register_lints) = compiler.register_lints() { - register_lints(sess, &mut lint_store); - true - } else { - false - }; - rustc_driver::describe_lints(sess, &lint_store, registered_lints); + rustc_driver::describe_lints(sess); return Ok(()); } compiler.enter(|queries| { let mut gcx = abort_on_err(queries.global_ctxt(), sess); - if sess.diagnostic().has_errors_or_lint_errors().is_some() { + if sess.dcx().has_errors_or_lint_errors().is_some() { sess.fatal("Compilation failed, aborting rustdoc"); } diff --git a/src/librustdoc/lint.rs b/src/librustdoc/lint.rs index d45040e34..267f1cb0b 100644 --- a/src/librustdoc/lint.rs +++ b/src/librustdoc/lint.rs @@ -33,7 +33,7 @@ where let lints = || { lint::builtin::HardwiredLints::get_lints() .into_iter() - .chain(rustc_lint::SoftLints::get_lints().into_iter()) + .chain(rustc_lint::SoftLints::get_lints()) }; let lint_opts = lints() @@ -46,7 +46,7 @@ where filter_call(lint) } }) - .chain(lint_opts.into_iter()) + .chain(lint_opts) .collect::<Vec<_>>(); let lint_caps = lints() diff --git a/src/librustdoc/passes/check_custom_code_classes.rs b/src/librustdoc/passes/check_custom_code_classes.rs index 6266d3ff5..73f71cc06 100644 --- a/src/librustdoc/passes/check_custom_code_classes.rs +++ b/src/librustdoc/passes/check_custom_code_classes.rs @@ -48,7 +48,7 @@ struct TestsWithCustomClasses { impl crate::doctest::Tester for TestsWithCustomClasses { fn add_test(&mut self, _: String, config: LangString, _: usize) { - self.custom_classes_found.extend(config.added_classes.into_iter()); + self.custom_classes_found.extend(config.added_classes); } } @@ -66,9 +66,8 @@ pub(crate) fn look_for_custom_classes<'tcx>(cx: &DocContext<'tcx>, item: &Item) if !tests.custom_classes_found.is_empty() { let span = item.attr_span(cx.tcx); let sess = &cx.tcx.sess.parse_sess; - let mut err = sess - .span_diagnostic - .struct_span_warn(span, "custom classes in code blocks will change behaviour"); + let mut err = + sess.dcx.struct_span_warn(span, "custom classes in code blocks will change behaviour"); add_feature_diagnostics_for_issue( &mut err, sess, diff --git a/src/librustdoc/passes/check_doc_test_visibility.rs b/src/librustdoc/passes/check_doc_test_visibility.rs index d1c4cc1f5..a931e8804 100644 --- a/src/librustdoc/passes/check_doc_test_visibility.rs +++ b/src/librustdoc/passes/check_doc_test_visibility.rs @@ -79,9 +79,9 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - let def_id = item.item_id.expect_def_id().expect_local(); // check if parent is trait impl - if let Some(parent_def_id) = cx.tcx.opt_local_parent(def_id) && - let Some(parent_node) = cx.tcx.hir().find_by_def_id(parent_def_id) && - matches!( + if let Some(parent_def_id) = cx.tcx.opt_local_parent(def_id) + && let Some(parent_node) = cx.tcx.opt_hir_node_by_def_id(parent_def_id) + && matches!( parent_node, hir::Node::Item(hir::Item { kind: hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }), @@ -100,7 +100,7 @@ pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) - } let (level, source) = cx.tcx.lint_level_at_node( crate::lint::MISSING_DOC_CODE_EXAMPLES, - cx.tcx.hir().local_def_id_to_hir_id(def_id), + cx.tcx.local_def_id_to_hir_id(def_id), ); level != lint::Level::Allow || matches!(source, LintLevelSource::Default) } @@ -131,7 +131,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item hir_id, sp, "missing code example in this documentation", - |lint| lint, + |_| {}, ); } } else if tests.found_tests > 0 @@ -142,7 +142,7 @@ pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item hir_id, item.attr_span(cx.tcx), "documentation test in private item", - |lint| lint, + |_| {}, ); } } diff --git a/src/librustdoc/passes/collect_intra_doc_links.rs b/src/librustdoc/passes/collect_intra_doc_links.rs index fcd078858..ee185ab98 100644 --- a/src/librustdoc/passes/collect_intra_doc_links.rs +++ b/src/librustdoc/passes/collect_intra_doc_links.rs @@ -303,7 +303,9 @@ impl<'a, 'tcx> LinkCollector<'a, 'tcx> { Res::Def(DefKind::Enum, did) => match tcx.type_of(did).instantiate_identity().kind() { ty::Adt(def, _) if def.is_enum() => { if let Some(variant) = def.variants().iter().find(|v| v.name == variant_name) - && let Some(field) = variant.fields.iter().find(|f| f.name == variant_field_name) { + && let Some(field) = + variant.fields.iter().find(|f| f.name == variant_field_name) + { Ok((ty_res, field.did)) } else { Err(UnresolvedPath { @@ -973,7 +975,8 @@ impl LinkCollector<'_, '_> { && let Some(def_id) = item.item_id.as_def_id() && let Some(def_id) = def_id.as_local() && !self.cx.tcx.effective_visibilities(()).is_exported(def_id) - && !has_primitive_or_keyword_docs(&item.attrs.other_attrs) { + && !has_primitive_or_keyword_docs(&item.attrs.other_attrs) + { // Skip link resolution for non-exported items. return; } @@ -1250,9 +1253,10 @@ impl LinkCollector<'_, '_> { // FIXME: it would be nice to check that the feature gate was enabled in the original crate, not just ignore it altogether. // However I'm not sure how to check that across crates. - if let Some(candidate) = candidates.get(0) && - candidate.0 == Res::Primitive(PrimitiveType::RawPointer) && - key.path_str.contains("::") // We only want to check this if this is an associated item. + if let Some(candidate) = candidates.get(0) + && candidate.0 == Res::Primitive(PrimitiveType::RawPointer) + && key.path_str.contains("::") + // We only want to check this if this is an associated item. { if key.item_id.is_local() && !self.cx.tcx.features().intra_doc_pointers { self.report_rawptr_assoc_feature_gate(diag.dox, &diag.link_range, diag.item); @@ -1318,8 +1322,8 @@ impl LinkCollector<'_, '_> { for other_ns in [TypeNS, ValueNS, MacroNS] { if other_ns != expected_ns { if let Ok(res) = - self.resolve(path_str, other_ns, item_id, module_id) && - !res.is_empty() + self.resolve(path_str, other_ns, item_id, module_id) + && !res.is_empty() { err = ResolutionFailure::WrongNamespace { res: full_res(self.cx.tcx, res[0]), @@ -1751,8 +1755,6 @@ fn report_diagnostic( } decorate(lint, span, link_range); - - lint }); } @@ -1892,8 +1894,10 @@ fn resolution_failure( }; let is_struct_variant = |did| { if let ty::Adt(def, _) = tcx.type_of(did).instantiate_identity().kind() - && def.is_enum() - && let Some(variant) = def.variants().iter().find(|v| v.name == res.name(tcx)) { + && def.is_enum() + && let Some(variant) = + def.variants().iter().find(|v| v.name == res.name(tcx)) + { // ctor is `None` if variant is a struct variant.ctor.is_none() } else { @@ -1918,7 +1922,6 @@ fn resolution_failure( Variant | Field | Closure - | Coroutine | AssocTy | AssocConst | AssocFn diff --git a/src/librustdoc/passes/collect_trait_impls.rs b/src/librustdoc/passes/collect_trait_impls.rs index a57321b58..df2e8584b 100644 --- a/src/librustdoc/passes/collect_trait_impls.rs +++ b/src/librustdoc/passes/collect_trait_impls.rs @@ -22,7 +22,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> let tcx = cx.tcx; // We need to check if there are errors before running this pass because it would crash when // we try to get auto and blanket implementations. - if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() { + if tcx.sess.dcx().has_errors_or_lint_errors().is_some() { return krate; } @@ -154,9 +154,9 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> // scan through included items ahead of time to splice in Deref targets to the "valid" sets for it in new_items_external.iter().chain(new_items_local.iter()) { - if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = *it.kind && - trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() && - cleaner.keep_impl(for_, true) + if let ImplItem(box Impl { ref for_, ref trait_, ref items, .. }) = *it.kind + && trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait() + && cleaner.keep_impl(for_, true) { let target = items .iter() @@ -198,7 +198,7 @@ pub(crate) fn collect_trait_impls(mut krate: Crate, cx: &mut DocContext<'_>) -> cleaner.keep_impl( for_, trait_.as_ref().map(|t| t.def_id()) == tcx.lang_items().deref_trait(), - ) || trait_.as_ref().map_or(false, |t| cleaner.keep_impl_with_def_id(t.def_id().into())) + ) || trait_.as_ref().is_some_and(|t| cleaner.keep_impl_with_def_id(t.def_id().into())) || kind.is_blanket() } else { true @@ -250,8 +250,8 @@ impl<'cache> DocVisitor for ItemAndAliasCollector<'cache> { fn visit_item(&mut self, i: &Item) { self.items.insert(i.item_id); - if let TypeAliasItem(alias) = &*i.kind && - let Some(did) = alias.type_.def_id(self.cache) + if let TypeAliasItem(alias) = &*i.kind + && let Some(did) = alias.type_.def_id(self.cache) { self.items.insert(ItemId::DefId(did)); } diff --git a/src/librustdoc/passes/lint/bare_urls.rs b/src/librustdoc/passes/lint/bare_urls.rs index 0c5cfffe1..bffa17da3 100644 --- a/src/librustdoc/passes/lint/bare_urls.rs +++ b/src/librustdoc/passes/lint/bare_urls.rs @@ -31,7 +31,7 @@ pub(super) fn visit_item(cx: &DocContext<'_>, item: &Item) { "use an automatic link instead", format!("<{url}>"), Applicability::MachineApplicable, - ) + ); }); }; diff --git a/src/librustdoc/passes/lint/check_code_block_syntax.rs b/src/librustdoc/passes/lint/check_code_block_syntax.rs index ac8a75a4f..ce42b9c20 100644 --- a/src/librustdoc/passes/lint/check_code_block_syntax.rs +++ b/src/librustdoc/passes/lint/check_code_block_syntax.rs @@ -3,7 +3,7 @@ use rustc_data_structures::sync::{Lock, Lrc}; use rustc_errors::{ emitter::Emitter, translation::{to_fluent_args, Translate}, - Applicability, Diagnostic, Handler, LazyFallbackBundle, + Applicability, DiagCtxt, Diagnostic, LazyFallbackBundle, }; use rustc_parse::parse_stream_from_source_str; use rustc_resolve::rustdoc::source_span_for_markdown_range; @@ -42,9 +42,9 @@ fn check_rust_syntax( let emitter = BufferEmitter { buffer: Lrc::clone(&buffer), fallback_bundle }; let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); - let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings(); + let dcx = DiagCtxt::with_emitter(Box::new(emitter)).disable_warnings(); let source = dox[code_block.code].to_owned(); - let sess = ParseSess::with_span_handler(handler, sm); + let sess = ParseSess::with_dcx(dcx, sm); let edition = code_block.lang_string.edition.unwrap_or_else(|| cx.tcx.sess.edition()); let expn_data = @@ -98,7 +98,7 @@ fn check_rust_syntax( // Finally build and emit the completed diagnostic. // All points of divergence have been handled earlier so this can be // done the same way whether the span is precise or not. - let hir_id = cx.tcx.hir().local_def_id_to_hir_id(local_id); + let hir_id = cx.tcx.local_def_id_to_hir_id(local_id); cx.tcx.struct_span_lint_hir(crate::lint::INVALID_RUST_CODEBLOCKS, hir_id, sp, msg, |lint| { let explanation = if is_ignore { "`ignore` code blocks require valid Rust code for syntax highlighting; \ @@ -131,8 +131,6 @@ fn check_rust_syntax( for message in buffer.messages.iter() { lint.note(message.clone()); } - - lint }); } diff --git a/src/librustdoc/passes/lint/html_tags.rs b/src/librustdoc/passes/lint/html_tags.rs index 00d15a3ca..90874c011 100644 --- a/src/librustdoc/passes/lint/html_tags.rs +++ b/src/librustdoc/passes/lint/html_tags.rs @@ -89,7 +89,7 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { if (generics_start > 0 && dox.as_bytes()[generics_start - 1] == b'<') || (generics_end < dox.len() && dox.as_bytes()[generics_end] == b'>') { - return lint; + return; } // multipart form is chosen here because ``Vec<i32>`` would be confusing. lint.multipart_suggestion( @@ -101,8 +101,6 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { Applicability::MaybeIncorrect, ); } - - lint }); }; @@ -213,7 +211,9 @@ fn extract_path_backwards(text: &str, end_pos: usize) -> Option<usize> { .take_while(|(_, c)| is_id_start(*c) || is_id_continue(*c)) .reduce(|_accum, item| item) .and_then(|(new_pos, c)| is_id_start(c).then_some(new_pos)); - if let Some(new_pos) = new_pos && current_pos != new_pos { + if let Some(new_pos) = new_pos + && current_pos != new_pos + { current_pos = new_pos; continue; } diff --git a/src/librustdoc/passes/lint/redundant_explicit_links.rs b/src/librustdoc/passes/lint/redundant_explicit_links.rs index 472781e7d..4491d20b4 100644 --- a/src/librustdoc/passes/lint/redundant_explicit_links.rs +++ b/src/librustdoc/passes/lint/redundant_explicit_links.rs @@ -181,8 +181,6 @@ fn check_inline_or_reference_unknown_redundancy( .span_label(display_span, "because label contains path that resolves to same destination") .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links") .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways); - - lint }); } @@ -234,8 +232,6 @@ fn check_reference_redundancy( .span_note(def_span, "referenced explicit link target defined here") .note("when a link's destination is not specified,\nthe label is used to resolve intra-doc links") .span_suggestion_with_style(link_span, "remove explicit link target", format!("[{}]", link_data.display_link), Applicability::MaybeIncorrect, SuggestionStyle::ShowAlways); - - lint }); } diff --git a/src/librustdoc/passes/lint/unescaped_backticks.rs b/src/librustdoc/passes/lint/unescaped_backticks.rs index 8b7fdd6ab..0893cd0b4 100644 --- a/src/librustdoc/passes/lint/unescaped_backticks.rs +++ b/src/librustdoc/passes/lint/unescaped_backticks.rs @@ -111,8 +111,6 @@ pub(crate) fn visit_item(cx: &DocContext<'_>, item: &Item) { } suggest_insertion(cx, item, &dox, lint, backtick_index, '\\', "if you meant to use a literal backtick, escape it"); - - lint }); } Event::Code(_) => { diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index b35618415..df955421b 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -208,24 +208,25 @@ impl<'a> DocFolder for ImplStripper<'a, '_> { // Because we don't inline in `maybe_inline_local` if the output format is JSON, // we need to make a special check for JSON output: we want to keep it unless it has // a `#[doc(hidden)]` attribute if the `for_` type is exported. - if let Some(did) = imp.for_.def_id(self.cache) && - !imp.for_.is_assoc_ty() && !self.should_keep_impl(&i, did) + if let Some(did) = imp.for_.def_id(self.cache) + && !imp.for_.is_assoc_ty() + && !self.should_keep_impl(&i, did) { debug!("ImplStripper: impl item for stripped type; removing"); return None; } - if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id()) && - !self.should_keep_impl(&i, did) { + if let Some(did) = imp.trait_.as_ref().map(|t| t.def_id()) + && !self.should_keep_impl(&i, did) + { debug!("ImplStripper: impl item for stripped trait; removing"); return None; } if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) { for typaram in generics { - if let Some(did) = typaram.def_id(self.cache) && !self.should_keep_impl(&i, did) + if let Some(did) = typaram.def_id(self.cache) + && !self.should_keep_impl(&i, did) { - debug!( - "ImplStripper: stripped item in trait's generics; removing impl" - ); + debug!("ImplStripper: stripped item in trait's generics; removing impl"); return None; } } diff --git a/src/librustdoc/scrape_examples.rs b/src/librustdoc/scrape_examples.rs index dd52deef6..a343d7afc 100644 --- a/src/librustdoc/scrape_examples.rs +++ b/src/librustdoc/scrape_examples.rs @@ -40,7 +40,7 @@ pub(crate) struct ScrapeExamplesOptions { impl ScrapeExamplesOptions { pub(crate) fn new( matches: &getopts::Matches, - diag: &rustc_errors::Handler, + dcx: &rustc_errors::DiagCtxt, ) -> Result<Option<Self>, i32> { let output_path = matches.opt_str("scrape-examples-output-path"); let target_crates = matches.opt_strs("scrape-examples-target-crate"); @@ -52,11 +52,11 @@ impl ScrapeExamplesOptions { scrape_tests, })), (Some(_), false, _) | (None, true, _) => { - diag.err("must use --scrape-examples-output-path and --scrape-examples-target-crate together"); + dcx.err("must use --scrape-examples-output-path and --scrape-examples-target-crate together"); Err(1) } (None, false, true) => { - diag.err("must use --scrape-examples-output-path and --scrape-examples-target-crate with --scrape-tests"); + dcx.err("must use --scrape-examples-output-path and --scrape-examples-target-crate with --scrape-tests"); Err(1) } (None, false, false) => Ok(None), @@ -311,7 +311,7 @@ pub(crate) fn run( // The visitor might have found a type error, which we need to // promote to a fatal error - if tcx.sess.diagnostic().has_errors_or_lint_errors().is_some() { + if tcx.sess.dcx().has_errors_or_lint_errors().is_some() { return Err(String::from("Compilation failed, aborting rustdoc")); } @@ -325,7 +325,7 @@ pub(crate) fn run( // Save output to provided path let mut encoder = FileEncoder::new(options.output_path).map_err(|e| e.to_string())?; calls.encode(&mut encoder); - encoder.finish().map_err(|e| e.to_string())?; + encoder.finish().map_err(|(_path, e)| e.to_string())?; Ok(()) }; @@ -337,10 +337,11 @@ pub(crate) fn run( Ok(()) } -// Note: the Handler must be passed in explicitly because sess isn't available while parsing options +// Note: the DiagCtxt must be passed in explicitly because sess isn't available while parsing +// options. pub(crate) fn load_call_locations( with_examples: Vec<String>, - diag: &rustc_errors::Handler, + dcx: &rustc_errors::DiagCtxt, ) -> Result<AllCallLocations, i32> { let inner = || { let mut all_calls: AllCallLocations = FxHashMap::default(); @@ -358,7 +359,7 @@ pub(crate) fn load_call_locations( }; inner().map_err(|e: String| { - diag.err(format!("failed to load examples: {e}")); + dcx.err(format!("failed to load examples: {e}")); 1 }) } diff --git a/src/librustdoc/theme.rs b/src/librustdoc/theme.rs index 8c1acbd73..98010b056 100644 --- a/src/librustdoc/theme.rs +++ b/src/librustdoc/theme.rs @@ -5,7 +5,7 @@ use std::iter::Peekable; use std::path::Path; use std::str::Chars; -use rustc_errors::Handler; +use rustc_errors::DiagCtxt; #[cfg(test)] mod tests; @@ -236,7 +236,7 @@ pub(crate) fn get_differences( pub(crate) fn test_theme_against<P: AsRef<Path>>( f: &P, origin: &FxHashMap<String, CssPath>, - diag: &Handler, + dcx: &DiagCtxt, ) -> (bool, Vec<String>) { let against = match fs::read_to_string(f) .map_err(|e| e.to_string()) @@ -244,7 +244,7 @@ pub(crate) fn test_theme_against<P: AsRef<Path>>( { Ok(c) => c, Err(e) => { - diag.struct_err(e).emit(); + dcx.struct_err(e).emit(); return (false, vec![]); } }; diff --git a/src/librustdoc/visit_ast.rs b/src/librustdoc/visit_ast.rs index 549fd67e3..907ea6d30 100644 --- a/src/librustdoc/visit_ast.rs +++ b/src/librustdoc/visit_ast.rs @@ -135,14 +135,16 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { // macro in the same module. let mut inserted = FxHashSet::default(); for child in self.cx.tcx.module_children_local(CRATE_DEF_ID) { - if !child.reexport_chain.is_empty() && - let Res::Def(DefKind::Macro(_), def_id) = child.res && - let Some(local_def_id) = def_id.as_local() && - self.cx.tcx.has_attr(def_id, sym::macro_export) && - inserted.insert(def_id) + if !child.reexport_chain.is_empty() + && let Res::Def(DefKind::Macro(_), def_id) = child.res + && let Some(local_def_id) = def_id.as_local() + && self.cx.tcx.has_attr(def_id, sym::macro_export) + && inserted.insert(def_id) { let item = self.cx.tcx.hir().expect_item(local_def_id); - top_level_module.items.insert((local_def_id, Some(item.ident.name)), (item, None, None)); + top_level_module + .items + .insert((local_def_id, Some(item.ident.name)), (item, None, None)); } } @@ -161,15 +163,16 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { .iter() .filter_map(|attr| { Cfg::parse(attr.meta_item()?) - .map_err(|e| self.cx.sess().diagnostic().span_err(e.span, e.msg)) + .map_err(|e| self.cx.sess().dcx().span_err(e.span, e.msg)) .ok() }) .collect::<Vec<_>>() }) - .chain( - [Cfg::Cfg(sym::test, None), Cfg::Cfg(sym::doc, None), Cfg::Cfg(sym::doctest, None)] - .into_iter(), - ) + .chain([ + Cfg::Cfg(sym::test, None), + Cfg::Cfg(sym::doc, None), + Cfg::Cfg(sym::doctest, None), + ]) .collect(); self.cx.cache.exact_paths = self.exact_paths; @@ -240,7 +243,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { }; let document_hidden = self.cx.render_options.document_hidden; - let use_attrs = tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(def_id)); + let use_attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(def_id)); // Don't inline `doc(hidden)` imports so they can be stripped at a later stage. let is_no_inline = use_attrs.lists(sym::doc).has_word(sym::no_inline) || (document_hidden && use_attrs.lists(sym::doc).has_word(sym::hidden)); @@ -273,7 +276,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { }; let is_private = !self.cx.cache.effective_visibilities.is_directly_public(tcx, ori_res_did); - let item = tcx.hir().get_by_def_id(res_did); + let item = tcx.hir_node_by_def_id(res_did); if !please_inline { let inherits_hidden = !document_hidden && inherits_doc_hidden(tcx, res_did, None); @@ -444,8 +447,7 @@ impl<'a, 'tcx> RustdocVisitor<'a, 'tcx> { continue; } - let attrs = - tcx.hir().attrs(tcx.hir().local_def_id_to_hir_id(item.owner_id.def_id)); + let attrs = tcx.hir().attrs(tcx.local_def_id_to_hir_id(item.owner_id.def_id)); // If there was a private module in the current path then don't bother inlining // anything as it will probably be stripped anyway. |