authorbors <bors@rust-lang.org> 2025-08-19 04:22:49 UTC
committerbors <bors@rust-lang.org> 2025-08-19 04:22:49 UTC
log8365fcb2b840c95eeb0bc377af8bd498fad22245
tree803b7bb87deb46b579af4ec13dc1f415d4ac4e73
parentb96868fa2ef174b0a5aeb3bf041b3a5b517f11f8
parent531ec858e9a1c69840f70d4f2a485c288f50fc11

Auto merge of #145589 - Zalathar:rollup-k97wtuq, r=Zalathar

Rollup of 19 pull requests Successful merges: - rust-lang/rust#140956 (`impl PartialEq<{str,String}> for {Path,PathBuf}`) - rust-lang/rust#141744 (Stabilize `ip_from`) - rust-lang/rust#142681 (Remove the `#[no_sanitize]` attribute in favor of `#[sanitize(xyz = "on|off")]`) - rust-lang/rust#142871 (Trivial improve doc for transpose ) - rust-lang/rust#144252 (Do not copy .rmeta files into the sysroot of the build compiler during check of rustc/std) - rust-lang/rust#144476 (rustdoc-search: search backend with partitioned suffix tree) - rust-lang/rust#144567 (Fix RISC-V Test Failures in ./x test for Multiple Codegen Cases) - rust-lang/rust#144804 (Don't warn on never to any `as` casts as unreachable) - rust-lang/rust#144960 ([RTE-513] Ignore sleep_until test on SGX) - rust-lang/rust#145013 (overhaul `&mut` suggestions in borrowck errors) - rust-lang/rust#145041 (rework GAT borrowck limitation error) - rust-lang/rust#145243 (take attr style into account in diagnostics) - rust-lang/rust#145405 (cleanup: use run_in_tmpdir in run-make/rustdoc-scrape-examples-paths) - rust-lang/rust#145432 (cg_llvm: Small cleanups to `owned_target_machine`) - rust-lang/rust#145484 (Remove `LlvmArchiveBuilder` and supporting code/bindings) - rust-lang/rust#145557 (Fix uplifting in `Assemble` step) - rust-lang/rust#145563 (Remove the `From` derive macro from prelude) - rust-lang/rust#145565 (Improve context of bootstrap errors in CI) - rust-lang/rust#145584 (interpret: avoid forcing all integer newtypes into memory during clear_provenance) Failed merges: - rust-lang/rust#145359 (Fix bug where `rustdoc-js` tester would not pick the right `search.js` file if there is more than one) - rust-lang/rust#145573 (Add an experimental unsafe(force_target_feature) attribute.) r? `@ghost` `@rustbot` modify labels: rollup

287 files changed, 11492 insertions(+), 6762 deletions(-)

Cargo.lock+10
......@@ -4812,6 +4812,7 @@ dependencies = [
48124812 "serde_json",
48134813 "sha2",
48144814 "smallvec",
4815 "stringdex",
48154816 "tempfile",
48164817 "threadpool",
48174818 "tracing",
......@@ -5225,6 +5226,15 @@ dependencies = [
52255226 "quote",
52265227]
52275228
5229[[package]]
5230name = "stringdex"
5231version = "0.0.1-alpha4"
5232source = "registry+https://github.com/rust-lang/crates.io-index"
5233checksum = "2841fd43df5b1ff1b042e167068a1fe9b163dc93041eae56ab2296859013a9a0"
5234dependencies = [
5235 "stacker",
5236]
5237
52285238[[package]]
52295239name = "strsim"
52305240version = "0.11.1"
compiler/rustc_attr_parsing/src/attributes/inline.rs+2-2
......@@ -62,8 +62,8 @@ impl<S: Stage> SingleAttributeParser<S> for InlineParser {
6262 }
6363 }
6464 ArgParser::NameValue(_) => {
65 let suggestions =
66 <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "inline");
65 let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
66 .suggestions(cx.attr_style, "inline");
6767 let span = cx.attr_span;
6868 cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span);
6969 return None;
compiler/rustc_attr_parsing/src/attributes/macro_attrs.rs+1-1
......@@ -107,7 +107,7 @@ impl<S: Stage> AttributeParser<S> for MacroUseParser {
107107 }
108108 }
109109 ArgParser::NameValue(_) => {
110 let suggestions = MACRO_USE_TEMPLATE.suggestions(false, sym::macro_use);
110 let suggestions = MACRO_USE_TEMPLATE.suggestions(cx.attr_style, sym::macro_use);
111111 cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
112112 num_suggestions: suggestions.len(),
113113 suggestions: DiagArgValue::StrListSepByAnd(
compiler/rustc_attr_parsing/src/attributes/must_use.rs+2-2
......@@ -35,8 +35,8 @@ impl<S: Stage> SingleAttributeParser<S> for MustUseParser {
3535 Some(value_str)
3636 }
3737 ArgParser::List(_) => {
38 let suggestions =
39 <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "must_use");
38 let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
39 .suggestions(cx.attr_style, "must_use");
4040 cx.emit_err(session_diagnostics::IllFormedAttributeInputLint {
4141 num_suggestions: suggestions.len(),
4242 suggestions: DiagArgValue::StrListSepByAnd(
compiler/rustc_attr_parsing/src/attributes/test_attrs.rs+3-3
......@@ -29,7 +29,7 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
2929 ArgParser::NameValue(name_value) => {
3030 let Some(str_value) = name_value.value_as_str() else {
3131 let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
32 .suggestions(false, "ignore");
32 .suggestions(cx.attr_style, "ignore");
3333 let span = cx.attr_span;
3434 cx.emit_lint(
3535 AttributeLintKind::IllFormedAttributeInput { suggestions },
......@@ -40,8 +40,8 @@ impl<S: Stage> SingleAttributeParser<S> for IgnoreParser {
4040 Some(str_value)
4141 }
4242 ArgParser::List(_) => {
43 let suggestions =
44 <Self as SingleAttributeParser<S>>::TEMPLATE.suggestions(false, "ignore");
43 let suggestions = <Self as SingleAttributeParser<S>>::TEMPLATE
44 .suggestions(cx.attr_style, "ignore");
4545 let span = cx.attr_span;
4646 cx.emit_lint(AttributeLintKind::IllFormedAttributeInput { suggestions }, span);
4747 return None;
compiler/rustc_attr_parsing/src/context.rs+17-1
......@@ -5,7 +5,7 @@ use std::sync::LazyLock;
55
66use itertools::Itertools;
77use private::Sealed;
8use rustc_ast::{self as ast, LitKind, MetaItemLit, NodeId};
8use rustc_ast::{self as ast, AttrStyle, LitKind, MetaItemLit, NodeId};
99use rustc_errors::{DiagCtxtHandle, Diagnostic};
1010use rustc_feature::{AttributeTemplate, Features};
1111use rustc_hir::attrs::AttributeKind;
......@@ -315,6 +315,7 @@ pub struct AcceptContext<'f, 'sess, S: Stage> {
315315 /// The span of the attribute currently being parsed
316316 pub(crate) attr_span: Span,
317317
318 pub(crate) attr_style: AttrStyle,
318319 /// The expected structure of the attribute.
319320 ///
320321 /// Used in reporting errors to give a hint to users what the attribute *should* look like.
......@@ -396,6 +397,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
396397 i.kind.is_bytestr().then(|| self.sess().source_map().start_point(i.span))
397398 }),
398399 },
400 attr_style: self.attr_style,
399401 })
400402 }
401403
......@@ -406,6 +408,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
406408 template: self.template.clone(),
407409 attribute: self.attr_path.clone(),
408410 reason: AttributeParseErrorReason::ExpectedIntegerLiteral,
411 attr_style: self.attr_style,
409412 })
410413 }
411414
......@@ -416,6 +419,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
416419 template: self.template.clone(),
417420 attribute: self.attr_path.clone(),
418421 reason: AttributeParseErrorReason::ExpectedList,
422 attr_style: self.attr_style,
419423 })
420424 }
421425
......@@ -426,6 +430,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
426430 template: self.template.clone(),
427431 attribute: self.attr_path.clone(),
428432 reason: AttributeParseErrorReason::ExpectedNoArgs,
433 attr_style: self.attr_style,
429434 })
430435 }
431436
......@@ -437,6 +442,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
437442 template: self.template.clone(),
438443 attribute: self.attr_path.clone(),
439444 reason: AttributeParseErrorReason::ExpectedIdentifier,
445 attr_style: self.attr_style,
440446 })
441447 }
442448
......@@ -449,6 +455,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
449455 template: self.template.clone(),
450456 attribute: self.attr_path.clone(),
451457 reason: AttributeParseErrorReason::ExpectedNameValue(name),
458 attr_style: self.attr_style,
452459 })
453460 }
454461
......@@ -460,6 +467,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
460467 template: self.template.clone(),
461468 attribute: self.attr_path.clone(),
462469 reason: AttributeParseErrorReason::DuplicateKey(key),
470 attr_style: self.attr_style,
463471 })
464472 }
465473
......@@ -472,6 +480,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
472480 template: self.template.clone(),
473481 attribute: self.attr_path.clone(),
474482 reason: AttributeParseErrorReason::UnexpectedLiteral,
483 attr_style: self.attr_style,
475484 })
476485 }
477486
......@@ -482,6 +491,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
482491 template: self.template.clone(),
483492 attribute: self.attr_path.clone(),
484493 reason: AttributeParseErrorReason::ExpectedSingleArgument,
494 attr_style: self.attr_style,
485495 })
486496 }
487497
......@@ -492,6 +502,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
492502 template: self.template.clone(),
493503 attribute: self.attr_path.clone(),
494504 reason: AttributeParseErrorReason::ExpectedAtLeastOneArgument,
505 attr_style: self.attr_style,
495506 })
496507 }
497508
......@@ -510,6 +521,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
510521 strings: false,
511522 list: false,
512523 },
524 attr_style: self.attr_style,
513525 })
514526 }
515527
......@@ -528,6 +540,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
528540 strings: false,
529541 list: true,
530542 },
543 attr_style: self.attr_style,
531544 })
532545 }
533546
......@@ -546,6 +559,7 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
546559 strings: true,
547560 list: false,
548561 },
562 attr_style: self.attr_style,
549563 })
550564 }
551565
......@@ -804,6 +818,7 @@ impl<'sess> AttributeParser<'sess, Early> {
804818 },
805819 },
806820 attr_span: attr.span,
821 attr_style: attr.style,
807822 template,
808823 attr_path: path.get_attribute_path(),
809824 };
......@@ -914,6 +929,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
914929 emit_lint: &mut emit_lint,
915930 },
916931 attr_span: lower_span(attr.span),
932 attr_style: attr.style,
917933 template: &accept.template,
918934 attr_path: path.get_attribute_path(),
919935 };
compiler/rustc_attr_parsing/src/session_diagnostics.rs+4-2
......@@ -1,6 +1,6 @@
11use std::num::IntErrorKind;
22
3use rustc_ast as ast;
3use rustc_ast::{self as ast, AttrStyle};
44use rustc_errors::codes::*;
55use rustc_errors::{
66 Applicability, Diag, DiagArgValue, DiagCtxtHandle, Diagnostic, EmissionGuarantee, Level,
......@@ -579,6 +579,7 @@ pub(crate) enum AttributeParseErrorReason {
579579pub(crate) struct AttributeParseError {
580580 pub(crate) span: Span,
581581 pub(crate) attr_span: Span,
582 pub(crate) attr_style: AttrStyle,
582583 pub(crate) template: AttributeTemplate,
583584 pub(crate) attribute: AttrPath,
584585 pub(crate) reason: AttributeParseErrorReason,
......@@ -717,7 +718,8 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError {
717718 if let Some(link) = self.template.docs {
718719 diag.note(format!("for more information, visit <{link}>"));
719720 }
720 let suggestions = self.template.suggestions(false, &name);
721 let suggestions = self.template.suggestions(self.attr_style, &name);
722
721723 diag.span_suggestions(
722724 self.attr_span,
723725 if suggestions.len() == 1 {
compiler/rustc_borrowck/messages.ftl+1-1
......@@ -90,7 +90,7 @@ borrowck_lifetime_constraints_error =
9090 lifetime may not live long enough
9191
9292borrowck_limitations_implies_static =
93 due to current limitations in the borrow checker, this implies a `'static` lifetime
93 due to a current limitation of the type system, this implies a `'static` lifetime
9494
9595borrowck_move_closure_suggestion =
9696 consider adding 'move' keyword before the nested closure
compiler/rustc_borrowck/src/diagnostics/mod.rs+52-9
......@@ -6,7 +6,9 @@ use rustc_abi::{FieldIdx, VariantIdx};
66use rustc_data_structures::fx::FxIndexMap;
77use rustc_errors::{Applicability, Diag, EmissionGuarantee, MultiSpan, listify};
88use rustc_hir::def::{CtorKind, Namespace};
9use rustc_hir::{self as hir, CoroutineKind, LangItem};
9use rustc_hir::{
10 self as hir, CoroutineKind, GenericBound, LangItem, WhereBoundPredicate, WherePredicateKind,
11};
1012use rustc_index::{IndexSlice, IndexVec};
1113use rustc_infer::infer::{BoundRegionConversionTime, NllRegionVariableOrigin};
1214use rustc_infer::traits::SelectionError;
......@@ -658,25 +660,66 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
658660
659661 /// Add a note to region errors and borrow explanations when higher-ranked regions in predicates
660662 /// implicitly introduce an "outlives `'static`" constraint.
663 ///
664 /// This is very similar to `fn suggest_static_lifetime_for_gat_from_hrtb` which handles this
665 /// note for failed type tests instead of outlives errors.
661666 fn add_placeholder_from_predicate_note<G: EmissionGuarantee>(
662667 &self,
663 err: &mut Diag<'_, G>,
668 diag: &mut Diag<'_, G>,
664669 path: &[OutlivesConstraint<'tcx>],
665670 ) {
666 let predicate_span = path.iter().find_map(|constraint| {
671 let tcx = self.infcx.tcx;
672 let Some((gat_hir_id, generics)) = path.iter().find_map(|constraint| {
667673 let outlived = constraint.sub;
668674 if let Some(origin) = self.regioncx.definitions.get(outlived)
669 && let NllRegionVariableOrigin::Placeholder(_) = origin.origin
670 && let ConstraintCategory::Predicate(span) = constraint.category
675 && let NllRegionVariableOrigin::Placeholder(placeholder) = origin.origin
676 && let Some(id) = placeholder.bound.kind.get_id()
677 && let Some(placeholder_id) = id.as_local()
678 && let gat_hir_id = tcx.local_def_id_to_hir_id(placeholder_id)
679 && let Some(generics_impl) =
680 tcx.parent_hir_node(tcx.parent_hir_id(gat_hir_id)).generics()
671681 {
672 Some(span)
682 Some((gat_hir_id, generics_impl))
673683 } else {
674684 None
675685 }
676 });
686 }) else {
687 return;
688 };
677689
678 if let Some(span) = predicate_span {
679 err.span_note(span, "due to current limitations in the borrow checker, this implies a `'static` lifetime");
690 // Look for the where-bound which introduces the placeholder.
691 // As we're using the HIR, we need to handle both `for<'a> T: Trait<'a>`
692 // and `T: for<'a> Trait`<'a>.
693 for pred in generics.predicates {
694 let WherePredicateKind::BoundPredicate(WhereBoundPredicate {
695 bound_generic_params,
696 bounds,
697 ..
698 }) = pred.kind
699 else {
700 continue;
701 };
702 if bound_generic_params
703 .iter()
704 .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id)
705 .is_some()
706 {
707 diag.span_note(pred.span, fluent::borrowck_limitations_implies_static);
708 return;
709 }
710 for bound in bounds.iter() {
711 if let GenericBound::Trait(bound) = bound {
712 if bound
713 .bound_generic_params
714 .iter()
715 .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id)
716 .is_some()
717 {
718 diag.span_note(bound.span, fluent::borrowck_limitations_implies_static);
719 return;
720 }
721 }
722 }
680723 }
681724 }
682725
compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs+408-365
......@@ -3,6 +3,7 @@
33
44use core::ops::ControlFlow;
55
6use either::Either;
67use hir::{ExprKind, Param};
78use rustc_abi::FieldIdx;
89use rustc_errors::{Applicability, Diag};
......@@ -12,15 +13,16 @@ use rustc_middle::bug;
1213use rustc_middle::hir::place::PlaceBase;
1314use rustc_middle::mir::visit::PlaceContext;
1415use rustc_middle::mir::{
15 self, BindingForm, Local, LocalDecl, LocalInfo, LocalKind, Location, Mutability, Place,
16 PlaceRef, ProjectionElem,
16 self, BindingForm, Body, BorrowKind, Local, LocalDecl, LocalInfo, LocalKind, Location,
17 Mutability, Operand, Place, PlaceRef, ProjectionElem, RawPtrKind, Rvalue, Statement,
18 StatementKind, TerminatorKind,
1719};
1820use rustc_middle::ty::{self, InstanceKind, Ty, TyCtxt, Upcast};
1921use rustc_span::{BytePos, DesugaringKind, Span, Symbol, kw, sym};
2022use rustc_trait_selection::error_reporting::InferCtxtErrorExt;
2123use rustc_trait_selection::infer::InferCtxtExt;
2224use rustc_trait_selection::traits;
23use tracing::debug;
25use tracing::{debug, trace};
2426
2527use crate::diagnostics::BorrowedContentSource;
2628use crate::{MirBorrowckCtxt, session_diagnostics};
......@@ -31,6 +33,33 @@ pub(crate) enum AccessKind {
3133 Mutate,
3234}
3335
36/// Finds all statements that assign directly to local (i.e., X = ...) and returns their
37/// locations.
38fn find_assignments(body: &Body<'_>, local: Local) -> Vec<Location> {
39 use rustc_middle::mir::visit::Visitor;
40
41 struct FindLocalAssignmentVisitor {
42 needle: Local,
43 locations: Vec<Location>,
44 }
45
46 impl<'tcx> Visitor<'tcx> for FindLocalAssignmentVisitor {
47 fn visit_local(&mut self, local: Local, place_context: PlaceContext, location: Location) {
48 if self.needle != local {
49 return;
50 }
51
52 if place_context.is_place_assignment() {
53 self.locations.push(location);
54 }
55 }
56 }
57
58 let mut visitor = FindLocalAssignmentVisitor { needle: local, locations: vec![] };
59 visitor.visit_body(body);
60 visitor.locations
61}
62
3463impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
3564 pub(crate) fn report_mutability_error(
3665 &mut self,
......@@ -384,7 +413,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
384413 }
385414 }
386415
387 // Also suggest adding mut for upvars
416 // Also suggest adding mut for upvars.
388417 PlaceRef {
389418 local,
390419 projection: [proj_base @ .., ProjectionElem::Field(upvar_index, _)],
......@@ -438,9 +467,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
438467 }
439468 }
440469
441 // complete hack to approximate old AST-borrowck
442 // diagnostic: if the span starts with a mutable borrow of
443 // a local variable, then just suggest the user remove it.
470 // Complete hack to approximate old AST-borrowck diagnostic: if the span starts
471 // with a mutable borrow of a local variable, then just suggest the user remove it.
444472 PlaceRef { local: _, projection: [] }
445473 if self
446474 .infcx
......@@ -769,7 +797,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
769797 );
770798 }
771799
772 // point to span of upvar making closure call require mutable borrow
800 // Point to span of upvar making closure call that requires a mutable borrow
773801 fn show_mutating_upvar(
774802 &self,
775803 tcx: TyCtxt<'_>,
......@@ -825,7 +853,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
825853 } else {
826854 bug!("not an upvar")
827855 };
828 // sometimes we deliberately don't store the name of a place when coming from a macro in
856 // Sometimes we deliberately don't store the name of a place when coming from a macro in
829857 // another crate. We generally want to limit those diagnostics a little, to hide
830858 // implementation details (such as those from pin!() or format!()). In that case show a
831859 // slightly different error message, or none at all if something else happened. In other
......@@ -936,8 +964,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
936964 let def_id = tcx.hir_enclosing_body_owner(fn_call_id);
937965 let mut look_at_return = true;
938966
939 // If the HIR node is a function or method call gets the def ID
940 // of the called function or method and the span and args of the call expr
967 // If the HIR node is a function or method call, get the DefId
968 // of the callee function or method, the span, and args of the call expr
941969 let get_call_details = || {
942970 let hir::Node::Expr(hir::Expr { hir_id, kind, .. }) = node else {
943971 return None;
......@@ -1051,7 +1079,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
10511079 let mut cur_expr = expr;
10521080 while let ExprKind::MethodCall(path_segment, recv, _, _) = cur_expr.kind {
10531081 if path_segment.ident.name == sym::iter {
1054 // check `_ty` has `iter_mut` method
1082 // Check that the type has an `iter_mut` method.
10551083 let res = self
10561084 .infcx
10571085 .tcx
......@@ -1081,38 +1109,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
10811109 }
10821110 }
10831111
1084 /// Finds all statements that assign directly to local (i.e., X = ...) and returns their
1085 /// locations.
1086 fn find_assignments(&self, local: Local) -> Vec<Location> {
1087 use rustc_middle::mir::visit::Visitor;
1088
1089 struct FindLocalAssignmentVisitor {
1090 needle: Local,
1091 locations: Vec<Location>,
1092 }
1093
1094 impl<'tcx> Visitor<'tcx> for FindLocalAssignmentVisitor {
1095 fn visit_local(
1096 &mut self,
1097 local: Local,
1098 place_context: PlaceContext,
1099 location: Location,
1100 ) {
1101 if self.needle != local {
1102 return;
1103 }
1104
1105 if place_context.is_place_assignment() {
1106 self.locations.push(location);
1107 }
1108 }
1109 }
1110
1111 let mut visitor = FindLocalAssignmentVisitor { needle: local, locations: vec![] };
1112 visitor.visit_body(self.body);
1113 visitor.locations
1114 }
1115
11161112 fn suggest_make_local_mut(&self, err: &mut Diag<'_>, local: Local, name: Symbol) {
11171113 let local_decl = &self.body.local_decls[local];
11181114
......@@ -1122,7 +1118,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
11221118 let (is_trait_sig, is_local, local_trait) = self.is_error_in_trait(local);
11231119
11241120 if is_trait_sig && !is_local {
1125 // Do not suggest to change the signature when the trait comes from another crate.
1121 // Do not suggest changing the signature when the trait comes from another crate.
11261122 err.span_label(
11271123 local_decl.source_info.span,
11281124 format!("this is an immutable {pointer_desc}"),
......@@ -1131,11 +1127,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
11311127 }
11321128 let decl_span = local_decl.source_info.span;
11331129
1134 let amp_mut_sugg = match *local_decl.local_info() {
1130 let (amp_mut_sugg, local_var_ty_info) = match *local_decl.local_info() {
11351131 LocalInfo::User(mir::BindingForm::ImplicitSelf(_)) => {
11361132 let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span);
11371133 let additional = local_trait.map(|span| suggest_ampmut_self(self.infcx.tcx, span));
1138 Some(AmpMutSugg { has_sugg: true, span, suggestion, additional })
1134 (AmpMutSugg::Type { span, suggestion, additional }, None)
11391135 }
11401136
11411137 LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm {
......@@ -1143,79 +1139,54 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
11431139 opt_ty_info,
11441140 ..
11451141 })) => {
1146 // check if the RHS is from desugaring
1142 // Check if the RHS is from desugaring.
1143 let first_assignment = find_assignments(&self.body, local).first().copied();
1144 let first_assignment_stmt = first_assignment
1145 .and_then(|loc| self.body[loc.block].statements.get(loc.statement_index));
1146 trace!(?first_assignment_stmt);
11471147 let opt_assignment_rhs_span =
1148 self.find_assignments(local).first().map(|&location| {
1149 if let Some(mir::Statement {
1150 source_info: _,
1151 kind:
1152 mir::StatementKind::Assign(box (
1153 _,
1154 mir::Rvalue::Use(mir::Operand::Copy(place)),
1155 )),
1156 ..
1157 }) = self.body[location.block].statements.get(location.statement_index)
1158 {
1159 self.body.local_decls[place.local].source_info.span
1160 } else {
1161 self.body.source_info(location).span
1162 }
1163 });
1164 match opt_assignment_rhs_span.and_then(|s| s.desugaring_kind()) {
1165 // on for loops, RHS points to the iterator part
1166 Some(DesugaringKind::ForLoop) => {
1167 let span = opt_assignment_rhs_span.unwrap();
1168 self.suggest_similar_mut_method_for_for_loop(err, span);
1148 first_assignment.map(|loc| self.body.source_info(loc).span);
1149 let mut source_span = opt_assignment_rhs_span;
1150 if let Some(mir::Statement {
1151 source_info: _,
1152 kind:
1153 mir::StatementKind::Assign(box (_, mir::Rvalue::Use(mir::Operand::Copy(place)))),
1154 ..
1155 }) = first_assignment_stmt
1156 {
1157 let local_span = self.body.local_decls[place.local].source_info.span;
1158 // `&self` in async functions have a `desugaring_kind`, but the local we assign
1159 // it with does not, so use the local_span for our checks later.
1160 source_span = Some(local_span);
1161 if let Some(DesugaringKind::ForLoop) = local_span.desugaring_kind() {
1162 // On for loops, RHS points to the iterator part.
1163 self.suggest_similar_mut_method_for_for_loop(err, local_span);
11691164 err.span_label(
1170 span,
1165 local_span,
11711166 format!("this iterator yields `{pointer_sigil}` {pointer_desc}s",),
11721167 );
1173 None
1174 }
1175 // don't create labels for compiler-generated spans
1176 Some(_) => None,
1177 // don't create labels for the span not from user's code
1178 None if opt_assignment_rhs_span
1179 .is_some_and(|span| self.infcx.tcx.sess.source_map().is_imported(span)) =>
1180 {
1181 None
1182 }
1183 None => {
1184 if name != kw::SelfLower {
1185 suggest_ampmut(
1186 self.infcx.tcx,
1187 local_decl.ty,
1188 decl_span,
1189 opt_assignment_rhs_span,
1190 opt_ty_info,
1191 )
1192 } else {
1193 match local_decl.local_info() {
1194 LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm {
1195 opt_ty_info: None,
1196 ..
1197 })) => {
1198 let (span, sugg) =
1199 suggest_ampmut_self(self.infcx.tcx, decl_span);
1200 Some(AmpMutSugg {
1201 has_sugg: true,
1202 span,
1203 suggestion: sugg,
1204 additional: None,
1205 })
1206 }
1207 // explicit self (eg `self: &'a Self`)
1208 _ => suggest_ampmut(
1209 self.infcx.tcx,
1210 local_decl.ty,
1211 decl_span,
1212 opt_assignment_rhs_span,
1213 opt_ty_info,
1214 ),
1215 }
1216 }
1168 return;
12171169 }
12181170 }
1171
1172 // Don't create labels for compiler-generated spans or spans not from users' code.
1173 if source_span.is_some_and(|s| {
1174 s.desugaring_kind().is_some() || self.infcx.tcx.sess.source_map().is_imported(s)
1175 }) {
1176 return;
1177 }
1178
1179 // This could be because we're in an `async fn`.
1180 if name == kw::SelfLower && opt_ty_info.is_none() {
1181 let (span, suggestion) = suggest_ampmut_self(self.infcx.tcx, decl_span);
1182 (AmpMutSugg::Type { span, suggestion, additional: None }, None)
1183 } else if let Some(sugg) =
1184 suggest_ampmut(self.infcx, self.body(), first_assignment_stmt)
1185 {
1186 (sugg, opt_ty_info)
1187 } else {
1188 return;
1189 }
12191190 }
12201191
12211192 LocalInfo::User(mir::BindingForm::Var(mir::VarBindingForm {
......@@ -1223,181 +1194,238 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
12231194 ..
12241195 })) => {
12251196 let pattern_span: Span = local_decl.source_info.span;
1226 suggest_ref_mut(self.infcx.tcx, pattern_span).map(|span| AmpMutSugg {
1227 has_sugg: true,
1228 span,
1229 suggestion: "mut ".to_owned(),
1230 additional: None,
1231 })
1197 let Some(span) = suggest_ref_mut(self.infcx.tcx, pattern_span) else {
1198 return;
1199 };
1200 (AmpMutSugg::Type { span, suggestion: "mut ".to_owned(), additional: None }, None)
12321201 }
12331202
12341203 _ => unreachable!(),
12351204 };
12361205
1237 match amp_mut_sugg {
1238 Some(AmpMutSugg {
1239 has_sugg: true,
1240 span: err_help_span,
1241 suggestion: suggested_code,
1242 additional,
1243 }) => {
1244 let mut sugg = vec![(err_help_span, suggested_code)];
1245 if let Some(s) = additional {
1246 sugg.push(s);
1247 }
1206 let mut suggest = |suggs: Vec<_>, applicability, extra| {
1207 if suggs.iter().any(|(span, _)| self.infcx.tcx.sess.source_map().is_imported(*span)) {
1208 return;
1209 }
12481210
1249 if sugg.iter().all(|(span, _)| !self.infcx.tcx.sess.source_map().is_imported(*span))
1250 {
1251 err.multipart_suggestion_verbose(
1252 format!(
1253 "consider changing this to be a mutable {pointer_desc}{}",
1254 if is_trait_sig {
1255 " in the `impl` method and the `trait` definition"
1256 } else {
1257 ""
1258 }
1259 ),
1260 sugg,
1261 Applicability::MachineApplicable,
1262 );
1211 err.multipart_suggestion_verbose(
1212 format!(
1213 "consider changing this to be a mutable {pointer_desc}{}{extra}",
1214 if is_trait_sig {
1215 " in the `impl` method and the `trait` definition"
1216 } else {
1217 ""
1218 }
1219 ),
1220 suggs,
1221 applicability,
1222 );
1223 };
1224
1225 let (mut sugg, add_type_annotation_if_not_exists) = match amp_mut_sugg {
1226 AmpMutSugg::Type { span, suggestion, additional } => {
1227 let mut sugg = vec![(span, suggestion)];
1228 sugg.extend(additional);
1229 suggest(sugg, Applicability::MachineApplicable, "");
1230 return;
1231 }
1232 AmpMutSugg::MapGetMut { span, suggestion } => {
1233 if self.infcx.tcx.sess.source_map().is_imported(span) {
1234 return;
12631235 }
1236 err.multipart_suggestion_verbose(
1237 "consider using `get_mut`",
1238 vec![(span, suggestion)],
1239 Applicability::MaybeIncorrect,
1240 );
1241 return;
12641242 }
1265 Some(AmpMutSugg {
1266 has_sugg: false, span: err_label_span, suggestion: message, ..
1267 }) => {
1268 let def_id = self.body.source.def_id();
1269 let hir_id = if let Some(local_def_id) = def_id.as_local()
1270 && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id)
1271 {
1272 BindingFinder { span: err_label_span }.visit_body(&body).break_value()
1273 } else {
1274 None
1275 };
1243 AmpMutSugg::Expr { span, suggestion } => {
1244 // `Expr` suggestions should change type annotations if they already exist (probably immut),
1245 // but do not add new type annotations.
1246 (vec![(span, suggestion)], false)
1247 }
1248 AmpMutSugg::ChangeBinding => (vec![], true),
1249 };
12761250
1277 if let Some(hir_id) = hir_id
1278 && let hir::Node::LetStmt(local) = self.infcx.tcx.hir_node(hir_id)
1279 {
1280 let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap());
1281 if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait()
1282 && let Some(expr) = local.init
1283 && let ty = tables.node_type_opt(expr.hir_id)
1284 && let Some(ty) = ty
1285 && let ty::Ref(..) = ty.kind()
1251 // Find a binding's type to make mutable.
1252 let (binding_exists, span) = match local_var_ty_info {
1253 // If this is a variable binding with an explicit type,
1254 // then we will suggest changing it to be mutable.
1255 // This is `Applicability::MachineApplicable`.
1256 Some(ty_span) => (true, ty_span),
1257
1258 // Otherwise, we'll suggest *adding* an annotated type, we'll suggest
1259 // the RHS's type for that.
1260 // This is `Applicability::HasPlaceholders`.
1261 None => (false, decl_span),
1262 };
1263
1264 if !binding_exists && !add_type_annotation_if_not_exists {
1265 suggest(sugg, Applicability::MachineApplicable, "");
1266 return;
1267 }
1268
1269 // If the binding already exists and is a reference with an explicit
1270 // lifetime, then we can suggest adding ` mut`. This is special-cased from
1271 // the path without an explicit lifetime.
1272 let (sugg_span, sugg_str, suggest_now) = if let Ok(src) = self.infcx.tcx.sess.source_map().span_to_snippet(span)
1273 && src.starts_with("&'")
1274 // Note that `&' a T` is invalid so this is correct.
1275 && let Some(ws_pos) = src.find(char::is_whitespace)
1276 {
1277 let span = span.with_lo(span.lo() + BytePos(ws_pos as u32)).shrink_to_lo();
1278 (span, " mut".to_owned(), true)
1279 // If there is already a binding, we modify it to be `mut`.
1280 } else if binding_exists {
1281 // Shrink the span to just after the `&` in `&variable`.
1282 let span = span.with_lo(span.lo() + BytePos(1)).shrink_to_lo();
1283 (span, "mut ".to_owned(), true)
1284 } else {
1285 // Otherwise, suggest that the user annotates the binding; We provide the
1286 // type of the local.
1287 let ty = local_decl.ty.builtin_deref(true).unwrap();
1288
1289 (span, format!("{}mut {}", if local_decl.ty.is_ref() { "&" } else { "*" }, ty), false)
1290 };
1291
1292 if suggest_now {
1293 // Suggest changing `&x` to `&mut x` and changing `&T` to `&mut T` at the same time.
1294 let has_change = !sugg.is_empty();
1295 sugg.push((sugg_span, sugg_str));
1296 suggest(
1297 sugg,
1298 Applicability::MachineApplicable,
1299 // FIXME(fee1-dead) this somehow doesn't fire
1300 if has_change { " and changing the binding's type" } else { "" },
1301 );
1302 return;
1303 } else if !sugg.is_empty() {
1304 suggest(sugg, Applicability::MachineApplicable, "");
1305 return;
1306 }
1307
1308 let def_id = self.body.source.def_id();
1309 let hir_id = if let Some(local_def_id) = def_id.as_local()
1310 && let Some(body) = self.infcx.tcx.hir_maybe_body_owned_by(local_def_id)
1311 {
1312 BindingFinder { span: sugg_span }.visit_body(&body).break_value()
1313 } else {
1314 None
1315 };
1316 let node = hir_id.map(|hir_id| self.infcx.tcx.hir_node(hir_id));
1317
1318 let Some(hir::Node::LetStmt(local)) = node else {
1319 err.span_label(
1320 sugg_span,
1321 format!("consider changing this binding's type to be: `{sugg_str}`"),
1322 );
1323 return;
1324 };
1325
1326 let tables = self.infcx.tcx.typeck(def_id.as_local().unwrap());
1327 if let Some(clone_trait) = self.infcx.tcx.lang_items().clone_trait()
1328 && let Some(expr) = local.init
1329 && let ty = tables.node_type_opt(expr.hir_id)
1330 && let Some(ty) = ty
1331 && let ty::Ref(..) = ty.kind()
1332 {
1333 match self
1334 .infcx
1335 .type_implements_trait_shallow(clone_trait, ty.peel_refs(), self.infcx.param_env)
1336 .as_deref()
1337 {
1338 Some([]) => {
1339 // FIXME: This error message isn't useful, since we're just
1340 // vaguely suggesting to clone a value that already
1341 // implements `Clone`.
1342 //
1343 // A correct suggestion here would take into account the fact
1344 // that inference may be affected by missing types on bindings,
1345 // etc., to improve "tests/ui/borrowck/issue-91206.stderr", for
1346 // example.
1347 }
1348 None => {
1349 if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind
1350 && segment.ident.name == sym::clone
12861351 {
1287 match self
1288 .infcx
1289 .type_implements_trait_shallow(
1290 clone_trait,
1291 ty.peel_refs(),
1292 self.infcx.param_env,
1293 )
1294 .as_deref()
1295 {
1296 Some([]) => {
1297 // FIXME: This error message isn't useful, since we're just
1298 // vaguely suggesting to clone a value that already
1299 // implements `Clone`.
1300 //
1301 // A correct suggestion here would take into account the fact
1302 // that inference may be affected by missing types on bindings,
1303 // etc., to improve "tests/ui/borrowck/issue-91206.stderr", for
1304 // example.
1305 }
1306 None => {
1307 if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) =
1308 expr.kind
1309 && segment.ident.name == sym::clone
1310 {
1311 err.span_help(
1312 span,
1313 format!(
1314 "`{}` doesn't implement `Clone`, so this call clones \
1352 err.span_help(
1353 span,
1354 format!(
1355 "`{}` doesn't implement `Clone`, so this call clones \
13151356 the reference `{ty}`",
1316 ty.peel_refs(),
1317 ),
1318 );
1319 }
1320 // The type doesn't implement Clone.
1321 let trait_ref = ty::Binder::dummy(ty::TraitRef::new(
1322 self.infcx.tcx,
1323 clone_trait,
1324 [ty.peel_refs()],
1325 ));
1326 let obligation = traits::Obligation::new(
1327 self.infcx.tcx,
1328 traits::ObligationCause::dummy(),
1329 self.infcx.param_env,
1330 trait_ref,
1331 );
1332 self.infcx.err_ctxt().suggest_derive(
1333 &obligation,
1334 err,
1335 trait_ref.upcast(self.infcx.tcx),
1336 );
1337 }
1338 Some(errors) => {
1339 if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) =
1340 expr.kind
1341 && segment.ident.name == sym::clone
1342 {
1343 err.span_help(
1344 span,
1345 format!(
1346 "`{}` doesn't implement `Clone` because its \
1357 ty.peel_refs(),
1358 ),
1359 );
1360 }
1361 // The type doesn't implement Clone.
1362 let trait_ref = ty::Binder::dummy(ty::TraitRef::new(
1363 self.infcx.tcx,
1364 clone_trait,
1365 [ty.peel_refs()],
1366 ));
1367 let obligation = traits::Obligation::new(
1368 self.infcx.tcx,
1369 traits::ObligationCause::dummy(),
1370 self.infcx.param_env,
1371 trait_ref,
1372 );
1373 self.infcx.err_ctxt().suggest_derive(
1374 &obligation,
1375 err,
1376 trait_ref.upcast(self.infcx.tcx),
1377 );
1378 }
1379 Some(errors) => {
1380 if let hir::ExprKind::MethodCall(segment, _rcvr, [], span) = expr.kind
1381 && segment.ident.name == sym::clone
1382 {
1383 err.span_help(
1384 span,
1385 format!(
1386 "`{}` doesn't implement `Clone` because its \
13471387 implementations trait bounds could not be met, so \
13481388 this call clones the reference `{ty}`",
1349 ty.peel_refs(),
1350 ),
1351 );
1352 err.note(format!(
1353 "the following trait bounds weren't met: {}",
1354 errors
1355 .iter()
1356 .map(|e| e.obligation.predicate.to_string())
1357 .collect::<Vec<_>>()
1358 .join("\n"),
1359 ));
1360 }
1361 // The type doesn't implement Clone because of unmet obligations.
1362 for error in errors {
1363 if let traits::FulfillmentErrorCode::Select(
1364 traits::SelectionError::Unimplemented,
1365 ) = error.code
1366 && let ty::PredicateKind::Clause(ty::ClauseKind::Trait(
1367 pred,
1368 )) = error.obligation.predicate.kind().skip_binder()
1369 {
1370 self.infcx.err_ctxt().suggest_derive(
1371 &error.obligation,
1372 err,
1373 error.obligation.predicate.kind().rebind(pred),
1374 );
1375 }
1376 }
1377 }
1378 }
1389 ty.peel_refs(),
1390 ),
1391 );
1392 err.note(format!(
1393 "the following trait bounds weren't met: {}",
1394 errors
1395 .iter()
1396 .map(|e| e.obligation.predicate.to_string())
1397 .collect::<Vec<_>>()
1398 .join("\n"),
1399 ));
13791400 }
1380 let (changing, span, sugg) = match local.ty {
1381 Some(ty) => ("changing", ty.span, message),
1382 None => {
1383 ("specifying", local.pat.span.shrink_to_hi(), format!(": {message}"))
1401 // The type doesn't implement Clone because of unmet obligations.
1402 for error in errors {
1403 if let traits::FulfillmentErrorCode::Select(
1404 traits::SelectionError::Unimplemented,
1405 ) = error.code
1406 && let ty::PredicateKind::Clause(ty::ClauseKind::Trait(pred)) =
1407 error.obligation.predicate.kind().skip_binder()
1408 {
1409 self.infcx.err_ctxt().suggest_derive(
1410 &error.obligation,
1411 err,
1412 error.obligation.predicate.kind().rebind(pred),
1413 );
13841414 }
1385 };
1386 err.span_suggestion_verbose(
1387 span,
1388 format!("consider {changing} this binding's type"),
1389 sugg,
1390 Applicability::HasPlaceholders,
1391 );
1392 } else {
1393 err.span_label(
1394 err_label_span,
1395 format!("consider changing this binding's type to be: `{message}`"),
1396 );
1415 }
13971416 }
13981417 }
1399 None => {}
14001418 }
1419 let (changing, span, sugg) = match local.ty {
1420 Some(ty) => ("changing", ty.span, sugg_str),
1421 None => ("specifying", local.pat.span.shrink_to_hi(), format!(": {sugg_str}")),
1422 };
1423 err.span_suggestion_verbose(
1424 span,
1425 format!("consider {changing} this binding's type"),
1426 sugg,
1427 Applicability::HasPlaceholders,
1428 );
14011429 }
14021430}
14031431
......@@ -1464,11 +1492,25 @@ fn suggest_ampmut_self(tcx: TyCtxt<'_>, span: Span) -> (Span, String) {
14641492 }
14651493}
14661494
1467struct AmpMutSugg {
1468 has_sugg: bool,
1469 span: Span,
1470 suggestion: String,
1471 additional: Option<(Span, String)>,
1495enum AmpMutSugg {
1496 /// Type suggestion. Changes `&self` to `&mut self`, `x: &T` to `x: &mut T`,
1497 /// `ref x` to `ref mut x`, etc.
1498 Type {
1499 span: Span,
1500 suggestion: String,
1501 additional: Option<(Span, String)>,
1502 },
1503 /// Suggestion for expressions, `&x` to `&mut x`, `&x[i]` to `&mut x[i]`, etc.
1504 Expr {
1505 span: Span,
1506 suggestion: String,
1507 },
1508 /// Suggests `.get_mut` in the case of `&map[&key]` for Hash/BTreeMap.
1509 MapGetMut {
1510 span: Span,
1511 suggestion: String,
1512 },
1513 ChangeBinding,
14721514}
14731515
14741516// When we want to suggest a user change a local variable to be a `&mut`, there
......@@ -1487,110 +1529,111 @@ struct AmpMutSugg {
14871529// This implementation attempts to emulate AST-borrowck prioritization
14881530// by trying (3.), then (2.) and finally falling back on (1.).
14891531fn suggest_ampmut<'tcx>(
1490 tcx: TyCtxt<'tcx>,
1491 decl_ty: Ty<'tcx>,
1492 decl_span: Span,
1493 opt_assignment_rhs_span: Option<Span>,
1494 opt_ty_info: Option<Span>,
1532 infcx: &crate::BorrowckInferCtxt<'tcx>,
1533 body: &Body<'tcx>,
1534 opt_assignment_rhs_stmt: Option<&Statement<'tcx>>,
14951535) -> Option<AmpMutSugg> {
1496 // if there is a RHS and it starts with a `&` from it, then check if it is
1536 let tcx = infcx.tcx;
1537 // If there is a RHS and it starts with a `&` from it, then check if it is
14971538 // mutable, and if not, put suggest putting `mut ` to make it mutable.
1498 // we don't have to worry about lifetime annotations here because they are
1539 // We don't have to worry about lifetime annotations here because they are
14991540 // not valid when taking a reference. For example, the following is not valid Rust:
15001541 //
15011542 // let x: &i32 = &'a 5;
15021543 // ^^ lifetime annotation not allowed
15031544 //
1504 if let Some(rhs_span) = opt_assignment_rhs_span
1505 && let Ok(rhs_str) = tcx.sess.source_map().span_to_snippet(rhs_span)
1506 && let Some(rhs_str_no_amp) = rhs_str.strip_prefix('&')
1545 if let Some(rhs_stmt) = opt_assignment_rhs_stmt
1546 && let StatementKind::Assign(box (lhs, rvalue)) = &rhs_stmt.kind
1547 && let mut rhs_span = rhs_stmt.source_info.span
1548 && let Ok(mut rhs_str) = tcx.sess.source_map().span_to_snippet(rhs_span)
15071549 {
1508 // Suggest changing `&raw const` to `&raw mut` if applicable.
1509 if rhs_str_no_amp.trim_start().strip_prefix("raw const").is_some() {
1510 let const_idx = rhs_str.find("const").unwrap() as u32;
1511 let const_span = rhs_span
1512 .with_lo(rhs_span.lo() + BytePos(const_idx))
1513 .with_hi(rhs_span.lo() + BytePos(const_idx + "const".len() as u32));
1514
1515 return Some(AmpMutSugg {
1516 has_sugg: true,
1517 span: const_span,
1518 suggestion: "mut".to_owned(),
1519 additional: None,
1520 });
1550 let mut rvalue = rvalue;
1551
1552 // Take some special care when handling `let _x = &*_y`:
1553 // We want to know if this is part of an overloaded index, so `let x = &a[0]`,
1554 // or whether this is a usertype ascription (`let _x: &T = y`).
1555 if let Rvalue::Ref(_, BorrowKind::Shared, place) = rvalue
1556 && place.projection.len() == 1
1557 && place.projection[0] == ProjectionElem::Deref
1558 && let Some(assign) = find_assignments(&body, place.local).first()
1559 {
1560 // If this is a usertype ascription (`let _x: &T = _y`) then pierce through it as either we want
1561 // to suggest `&mut` on the expression (handled here) or we return `None` and let the caller
1562 // suggest `&mut` on the type if the expression seems fine (e.g. `let _x: &T = &mut _y`).
1563 if let Some(user_ty_projs) = body.local_decls[lhs.local].user_ty.as_ref()
1564 && let [user_ty_proj] = user_ty_projs.contents.as_slice()
1565 && user_ty_proj.projs.is_empty()
1566 && let Either::Left(rhs_stmt_new) = body.stmt_at(*assign)
1567 && let StatementKind::Assign(box (_, rvalue_new)) = &rhs_stmt_new.kind
1568 && let rhs_span_new = rhs_stmt_new.source_info.span
1569 && let Ok(rhs_str_new) = tcx.sess.source_map().span_to_snippet(rhs_span)
1570 {
1571 (rvalue, rhs_span, rhs_str) = (rvalue_new, rhs_span_new, rhs_str_new);
1572 }
1573
1574 if let Either::Right(call) = body.stmt_at(*assign)
1575 && let TerminatorKind::Call {
1576 func: Operand::Constant(box const_operand), args, ..
1577 } = &call.kind
1578 && let ty::FnDef(method_def_id, method_args) = *const_operand.ty().kind()
1579 && let Some(trait_) = tcx.trait_of_assoc(method_def_id)
1580 && tcx.is_lang_item(trait_, hir::LangItem::Index)
1581 {
1582 let trait_ref = ty::TraitRef::from_assoc(
1583 tcx,
1584 tcx.require_lang_item(hir::LangItem::IndexMut, rhs_span),
1585 method_args,
1586 );
1587 // The type only implements `Index` but not `IndexMut`, we must not suggest `&mut`.
1588 if !infcx
1589 .type_implements_trait(trait_ref.def_id, trait_ref.args, infcx.param_env)
1590 .must_apply_considering_regions()
1591 {
1592 // Suggest `get_mut` if type is a `BTreeMap` or `HashMap`.
1593 if let ty::Adt(def, _) = trait_ref.self_ty().kind()
1594 && [sym::BTreeMap, sym::HashMap]
1595 .into_iter()
1596 .any(|s| tcx.is_diagnostic_item(s, def.did()))
1597 && let [map, key] = &**args
1598 && let Ok(map) = tcx.sess.source_map().span_to_snippet(map.span)
1599 && let Ok(key) = tcx.sess.source_map().span_to_snippet(key.span)
1600 {
1601 let span = rhs_span;
1602 let suggestion = format!("{map}.get_mut({key}).unwrap()");
1603 return Some(AmpMutSugg::MapGetMut { span, suggestion });
1604 }
1605 return None;
1606 }
1607 }
15211608 }
15221609
1523 // Figure out if rhs already is `&mut`.
1524 let is_mut = if let Some(rest) = rhs_str_no_amp.trim_start().strip_prefix("mut") {
1525 match rest.chars().next() {
1526 // e.g. `&mut x`
1527 Some(c) if c.is_whitespace() => true,
1528 // e.g. `&mut(x)`
1529 Some('(') => true,
1530 // e.g. `&mut{x}`
1531 Some('{') => true,
1532 // e.g. `&mutablevar`
1533 _ => false,
1610 let sugg = match rvalue {
1611 Rvalue::Ref(_, BorrowKind::Shared, _) if let Some(ref_idx) = rhs_str.find('&') => {
1612 // Shrink the span to just after the `&` in `&variable`.
1613 Some((
1614 rhs_span.with_lo(rhs_span.lo() + BytePos(ref_idx as u32 + 1)).shrink_to_lo(),
1615 "mut ".to_owned(),
1616 ))
15341617 }
1535 } else {
1536 false
1618 Rvalue::RawPtr(RawPtrKind::Const, _) if let Some(const_idx) = rhs_str.find("const") => {
1619 // Suggest changing `&raw const` to `&raw mut` if applicable.
1620 let const_idx = const_idx as u32;
1621 Some((
1622 rhs_span
1623 .with_lo(rhs_span.lo() + BytePos(const_idx))
1624 .with_hi(rhs_span.lo() + BytePos(const_idx + "const".len() as u32)),
1625 "mut".to_owned(),
1626 ))
1627 }
1628 _ => None,
15371629 };
1538 // if the reference is already mutable then there is nothing we can do
1539 // here.
1540 if !is_mut {
1541 // shrink the span to just after the `&` in `&variable`
1542 let span = rhs_span.with_lo(rhs_span.lo() + BytePos(1)).shrink_to_lo();
1543
1544 // FIXME(Ezrashaw): returning is bad because we still might want to
1545 // update the annotated type, see #106857.
1546 return Some(AmpMutSugg {
1547 has_sugg: true,
1548 span,
1549 suggestion: "mut ".to_owned(),
1550 additional: None,
1551 });
1630
1631 if let Some((span, suggestion)) = sugg {
1632 return Some(AmpMutSugg::Expr { span, suggestion });
15521633 }
15531634 }
15541635
1555 let (binding_exists, span) = match opt_ty_info {
1556 // if this is a variable binding with an explicit type,
1557 // then we will suggest changing it to be mutable.
1558 // this is `Applicability::MachineApplicable`.
1559 Some(ty_span) => (true, ty_span),
1560
1561 // otherwise, we'll suggest *adding* an annotated type, we'll suggest
1562 // the RHS's type for that.
1563 // this is `Applicability::HasPlaceholders`.
1564 None => (false, decl_span),
1565 };
1566
1567 // if the binding already exists and is a reference with an explicit
1568 // lifetime, then we can suggest adding ` mut`. this is special-cased from
1569 // the path without an explicit lifetime.
1570 if let Ok(src) = tcx.sess.source_map().span_to_snippet(span)
1571 && src.starts_with("&'")
1572 // note that `& 'a T` is invalid so this is correct.
1573 && let Some(ws_pos) = src.find(char::is_whitespace)
1574 {
1575 let span = span.with_lo(span.lo() + BytePos(ws_pos as u32)).shrink_to_lo();
1576 Some(AmpMutSugg { has_sugg: true, span, suggestion: " mut".to_owned(), additional: None })
1577 // if there is already a binding, we modify it to be `mut`
1578 } else if binding_exists {
1579 // shrink the span to just after the `&` in `&variable`
1580 let span = span.with_lo(span.lo() + BytePos(1)).shrink_to_lo();
1581 Some(AmpMutSugg { has_sugg: true, span, suggestion: "mut ".to_owned(), additional: None })
1582 } else {
1583 // otherwise, suggest that the user annotates the binding; we provide the
1584 // type of the local.
1585 let ty = decl_ty.builtin_deref(true).unwrap();
1586
1587 Some(AmpMutSugg {
1588 has_sugg: false,
1589 span,
1590 suggestion: format!("{}mut {}", if decl_ty.is_ref() { "&" } else { "*" }, ty),
1591 additional: None,
1592 })
1593 }
1636 Some(AmpMutSugg::ChangeBinding)
15941637}
15951638
15961639/// If the type is a `Coroutine`, `Closure`, or `CoroutineClosure`
compiler/rustc_borrowck/src/diagnostics/region_errors.rs+20-4
......@@ -215,7 +215,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
215215 diag: &mut Diag<'_>,
216216 lower_bound: RegionVid,
217217 ) {
218 let mut suggestions = vec![];
219218 let tcx = self.infcx.tcx;
220219
221220 // find generic associated types in the given region 'lower_bound'
......@@ -237,9 +236,11 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
237236 .collect::<Vec<_>>();
238237 debug!(?gat_id_and_generics);
239238
240 // find higher-ranked trait bounds bounded to the generic associated types
239 // Look for the where-bound which introduces the placeholder.
240 // As we're using the HIR, we need to handle both `for<'a> T: Trait<'a>`
241 // and `T: for<'a> Trait`<'a>.
241242 let mut hrtb_bounds = vec![];
242 gat_id_and_generics.iter().flatten().for_each(|(gat_hir_id, generics)| {
243 gat_id_and_generics.iter().flatten().for_each(|&(gat_hir_id, generics)| {
243244 for pred in generics.predicates {
244245 let BoundPredicate(WhereBoundPredicate { bound_generic_params, bounds, .. }) =
245246 pred.kind
......@@ -248,17 +249,32 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
248249 };
249250 if bound_generic_params
250251 .iter()
251 .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == *gat_hir_id)
252 .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id)
252253 .is_some()
253254 {
254255 for bound in *bounds {
255256 hrtb_bounds.push(bound);
256257 }
258 } else {
259 for bound in *bounds {
260 if let Trait(trait_bound) = bound {
261 if trait_bound
262 .bound_generic_params
263 .iter()
264 .rfind(|bgp| tcx.local_def_id_to_hir_id(bgp.def_id) == gat_hir_id)
265 .is_some()
266 {
267 hrtb_bounds.push(bound);
268 return;
269 }
270 }
271 }
257272 }
258273 }
259274 });
260275 debug!(?hrtb_bounds);
261276
277 let mut suggestions = vec![];
262278 hrtb_bounds.iter().for_each(|bound| {
263279 let Trait(PolyTraitRef { trait_ref, span: trait_span, .. }) = bound else {
264280 return;
compiler/rustc_codegen_llvm/src/back/archive.rs+6-177
......@@ -1,104 +1,21 @@
11//! A helper class for dealing with static archives
22
3use std::ffi::{CStr, CString, c_char, c_void};
4use std::path::{Path, PathBuf};
5use std::{io, mem, ptr, str};
3use std::ffi::{CStr, c_char, c_void};
4use std::io;
65
76use rustc_codegen_ssa::back::archive::{
8 ArArchiveBuilder, ArchiveBuildFailure, ArchiveBuilder, ArchiveBuilderBuilder,
9 DEFAULT_OBJECT_READER, ObjectReader, UnknownArchiveKind, try_extract_macho_fat_archive,
7 ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, ObjectReader,
108};
119use rustc_session::Session;
1210
13use crate::llvm::archive_ro::{ArchiveRO, Child};
14use crate::llvm::{self, ArchiveKind, last_error};
15
16/// Helper for adding many files to an archive.
17#[must_use = "must call build() to finish building the archive"]
18pub(crate) struct LlvmArchiveBuilder<'a> {
19 sess: &'a Session,
20 additions: Vec<Addition>,
21}
22
23enum Addition {
24 File { path: PathBuf, name_in_archive: String },
25 Archive { path: PathBuf, archive: ArchiveRO, skip: Box<dyn FnMut(&str) -> bool> },
26}
27
28impl Addition {
29 fn path(&self) -> &Path {
30 match self {
31 Addition::File { path, .. } | Addition::Archive { path, .. } => path,
32 }
33 }
34}
35
36fn is_relevant_child(c: &Child<'_>) -> bool {
37 match c.name() {
38 Some(name) => !name.contains("SYMDEF"),
39 None => false,
40 }
41}
42
43impl<'a> ArchiveBuilder for LlvmArchiveBuilder<'a> {
44 fn add_archive(
45 &mut self,
46 archive: &Path,
47 skip: Box<dyn FnMut(&str) -> bool + 'static>,
48 ) -> io::Result<()> {
49 let mut archive = archive.to_path_buf();
50 if self.sess.target.llvm_target.contains("-apple-macosx") {
51 if let Some(new_archive) = try_extract_macho_fat_archive(self.sess, &archive)? {
52 archive = new_archive
53 }
54 }
55 let archive_ro = match ArchiveRO::open(&archive) {
56 Ok(ar) => ar,
57 Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
58 };
59 if self.additions.iter().any(|ar| ar.path() == archive) {
60 return Ok(());
61 }
62 self.additions.push(Addition::Archive {
63 path: archive,
64 archive: archive_ro,
65 skip: Box::new(skip),
66 });
67 Ok(())
68 }
69
70 /// Adds an arbitrary file to this archive
71 fn add_file(&mut self, file: &Path) {
72 let name = file.file_name().unwrap().to_str().unwrap();
73 self.additions
74 .push(Addition::File { path: file.to_path_buf(), name_in_archive: name.to_owned() });
75 }
76
77 /// Combine the provided files, rlibs, and native libraries into a single
78 /// `Archive`.
79 fn build(mut self: Box<Self>, output: &Path) -> bool {
80 match self.build_with_llvm(output) {
81 Ok(any_members) => any_members,
82 Err(error) => {
83 self.sess.dcx().emit_fatal(ArchiveBuildFailure { path: output.to_owned(), error })
84 }
85 }
86 }
87}
11use crate::llvm;
8812
8913pub(crate) struct LlvmArchiveBuilderBuilder;
9014
9115impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder {
9216 fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box<dyn ArchiveBuilder + 'a> {
93 // Keeping LlvmArchiveBuilder around in case of a regression caused by using
94 // ArArchiveBuilder.
95 // FIXME(#128955) remove a couple of months after #128936 gets merged in case
96 // no regression is found.
97 if false {
98 Box::new(LlvmArchiveBuilder { sess, additions: Vec::new() })
99 } else {
100 Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER))
101 }
17 // Use the `object` crate to build archives, with a little bit of help from LLVM.
18 Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER))
10219 }
10320}
10421
......@@ -178,91 +95,3 @@ fn llvm_is_64_bit_object_file(buf: &[u8]) -> bool {
17895fn llvm_is_ec_object_file(buf: &[u8]) -> bool {
17996 unsafe { llvm::LLVMRustIsECObject(buf.as_ptr(), buf.len()) }
18097}
181
182impl<'a> LlvmArchiveBuilder<'a> {
183 fn build_with_llvm(&mut self, output: &Path) -> io::Result<bool> {
184 let kind = &*self.sess.target.archive_format;
185 let kind = kind
186 .parse::<ArchiveKind>()
187 .map_err(|_| kind)
188 .unwrap_or_else(|kind| self.sess.dcx().emit_fatal(UnknownArchiveKind { kind }));
189
190 let mut additions = mem::take(&mut self.additions);
191 // Values in the `members` list below will contain pointers to the strings allocated here.
192 // So they need to get dropped after all elements of `members` get freed.
193 let mut strings = Vec::new();
194 let mut members = Vec::new();
195
196 let dst = CString::new(output.to_str().unwrap())?;
197
198 unsafe {
199 for addition in &mut additions {
200 match addition {
201 Addition::File { path, name_in_archive } => {
202 let path = CString::new(path.to_str().unwrap())?;
203 let name = CString::new(name_in_archive.as_bytes())?;
204 members.push(llvm::LLVMRustArchiveMemberNew(
205 path.as_ptr(),
206 name.as_ptr(),
207 None,
208 ));
209 strings.push(path);
210 strings.push(name);
211 }
212 Addition::Archive { archive, skip, .. } => {
213 for child in archive.iter() {
214 let child = child.map_err(string_to_io_error)?;
215 if !is_relevant_child(&child) {
216 continue;
217 }
218 let child_name = child.name().unwrap();
219 if skip(child_name) {
220 continue;
221 }
222
223 // It appears that LLVM's archive writer is a little
224 // buggy if the name we pass down isn't just the
225 // filename component, so chop that off here and
226 // pass it in.
227 //
228 // See LLVM bug 25877 for more info.
229 let child_name =
230 Path::new(child_name).file_name().unwrap().to_str().unwrap();
231 let name = CString::new(child_name)?;
232 let m = llvm::LLVMRustArchiveMemberNew(
233 ptr::null(),
234 name.as_ptr(),
235 Some(child.raw),
236 );
237 members.push(m);
238 strings.push(name);
239 }
240 }
241 }
242 }
243
244 let r = llvm::LLVMRustWriteArchive(
245 dst.as_ptr(),
246 members.len() as libc::size_t,
247 members.as_ptr() as *const &_,
248 true,
249 kind,
250 self.sess.target.arch == "arm64ec",
251 );
252 let ret = if r.into_result().is_err() {
253 let msg = last_error().unwrap_or_else(|| "failed to write archive".into());
254 Err(io::Error::new(io::ErrorKind::Other, msg))
255 } else {
256 Ok(!members.is_empty())
257 };
258 for member in members {
259 llvm::LLVMRustArchiveMemberFree(member);
260 }
261 ret
262 }
263 }
264}
265
266fn string_to_io_error(s: String) -> io::Error {
267 io::Error::new(io::ErrorKind::Other, format!("bad archive: {s}"))
268}
compiler/rustc_codegen_llvm/src/back/mod.rs created+5
......@@ -0,0 +1,5 @@
1pub(crate) mod archive;
2pub(crate) mod lto;
3pub(crate) mod owned_target_machine;
4mod profiling;
5pub(crate) mod write;
compiler/rustc_codegen_llvm/src/back/owned_target_machine.rs+7-8
......@@ -1,4 +1,5 @@
1use std::ffi::{CStr, c_char};
1use std::assert_matches::assert_matches;
2use std::ffi::CStr;
23use std::marker::PhantomData;
34use std::ptr::NonNull;
45
......@@ -41,11 +42,9 @@ impl OwnedTargetMachine {
4142 args_cstr_buff: &[u8],
4243 use_wasm_eh: bool,
4344 ) -> Result<Self, LlvmError<'static>> {
44 assert!(args_cstr_buff.len() > 0);
45 assert!(
46 *args_cstr_buff.last().unwrap() == 0,
47 "The last character must be a null terminator."
48 );
45 // The argument list is passed as the concatenation of one or more C strings.
46 // This implies that there must be a last byte, and it must be 0.
47 assert_matches!(args_cstr_buff, [.., b'\0'], "the last byte must be a NUL terminator");
4948
5049 // SAFETY: llvm::LLVMRustCreateTargetMachine copies pointed to data
5150 let tm_ptr = unsafe {
......@@ -71,7 +70,7 @@ impl OwnedTargetMachine {
7170 output_obj_file.as_ptr(),
7271 debug_info_compression.as_ptr(),
7372 use_emulated_tls,
74 args_cstr_buff.as_ptr() as *const c_char,
73 args_cstr_buff.as_ptr(),
7574 args_cstr_buff.len(),
7675 use_wasm_eh,
7776 )
......@@ -99,7 +98,7 @@ impl Drop for OwnedTargetMachine {
9998 // llvm::LLVMRustCreateTargetMachine OwnedTargetMachine is not copyable so there is no
10099 // double free or use after free.
101100 unsafe {
102 llvm::LLVMRustDisposeTargetMachine(self.tm_unique.as_mut());
101 llvm::LLVMRustDisposeTargetMachine(self.tm_unique.as_ptr());
103102 }
104103 }
105104}
compiler/rustc_codegen_llvm/src/lib.rs+1-8
......@@ -46,18 +46,11 @@ use rustc_session::Session;
4646use rustc_session::config::{OptLevel, OutputFilenames, PrintKind, PrintRequest};
4747use rustc_span::Symbol;
4848
49mod back {
50 pub(crate) mod archive;
51 pub(crate) mod lto;
52 pub(crate) mod owned_target_machine;
53 mod profiling;
54 pub(crate) mod write;
55}
56
5749mod abi;
5850mod allocator;
5951mod asm;
6052mod attributes;
53mod back;
6154mod base;
6255mod builder;
6356mod callee;
compiler/rustc_codegen_llvm/src/llvm/archive_ro.rs deleted-94
......@@ -1,94 +0,0 @@
1//! A wrapper around LLVM's archive (.a) code
2
3use std::path::Path;
4use std::{slice, str};
5
6use rustc_fs_util::path_to_c_string;
7
8pub(crate) struct ArchiveRO {
9 pub raw: &'static mut super::Archive,
10}
11
12unsafe impl Send for ArchiveRO {}
13
14pub(crate) struct Iter<'a> {
15 raw: &'a mut super::ArchiveIterator<'a>,
16}
17
18pub(crate) struct Child<'a> {
19 pub raw: &'a mut super::ArchiveChild<'a>,
20}
21
22impl ArchiveRO {
23 /// Opens a static archive for read-only purposes. This is more optimized
24 /// than the `open` method because it uses LLVM's internal `Archive` class
25 /// rather than shelling out to `ar` for everything.
26 ///
27 /// If this archive is used with a mutable method, then an error will be
28 /// raised.
29 pub(crate) fn open(dst: &Path) -> Result<ArchiveRO, String> {
30 unsafe {
31 let s = path_to_c_string(dst);
32 let ar = super::LLVMRustOpenArchive(s.as_ptr()).ok_or_else(|| {
33 super::last_error().unwrap_or_else(|| "failed to open archive".to_owned())
34 })?;
35 Ok(ArchiveRO { raw: ar })
36 }
37 }
38
39 pub(crate) fn iter(&self) -> Iter<'_> {
40 unsafe { Iter { raw: super::LLVMRustArchiveIteratorNew(self.raw) } }
41 }
42}
43
44impl Drop for ArchiveRO {
45 fn drop(&mut self) {
46 unsafe {
47 super::LLVMRustDestroyArchive(&mut *(self.raw as *mut _));
48 }
49 }
50}
51
52impl<'a> Iterator for Iter<'a> {
53 type Item = Result<Child<'a>, String>;
54
55 fn next(&mut self) -> Option<Result<Child<'a>, String>> {
56 unsafe {
57 match super::LLVMRustArchiveIteratorNext(self.raw) {
58 Some(raw) => Some(Ok(Child { raw })),
59 None => super::last_error().map(Err),
60 }
61 }
62 }
63}
64
65impl<'a> Drop for Iter<'a> {
66 fn drop(&mut self) {
67 unsafe {
68 super::LLVMRustArchiveIteratorFree(&mut *(self.raw as *mut _));
69 }
70 }
71}
72
73impl<'a> Child<'a> {
74 pub(crate) fn name(&self) -> Option<&'a str> {
75 unsafe {
76 let mut name_len = 0;
77 let name_ptr = super::LLVMRustArchiveChildName(self.raw, &mut name_len);
78 if name_ptr.is_null() {
79 None
80 } else {
81 let name = slice::from_raw_parts(name_ptr as *const u8, name_len as usize);
82 str::from_utf8(name).ok().map(|s| s.trim())
83 }
84 }
85 }
86}
87
88impl<'a> Drop for Child<'a> {
89 fn drop(&mut self) {
90 unsafe {
91 super::LLVMRustArchiveChildFree(&mut *(self.raw as *mut _));
92 }
93 }
94}
compiler/rustc_codegen_llvm/src/llvm/ffi.rs+1-47
......@@ -616,17 +616,6 @@ pub(crate) enum DiagnosticLevel {
616616 Remark,
617617}
618618
619/// LLVMRustArchiveKind
620#[derive(Copy, Clone)]
621#[repr(C)]
622pub(crate) enum ArchiveKind {
623 K_GNU,
624 K_BSD,
625 K_DARWIN,
626 K_COFF,
627 K_AIXBIG,
628}
629
630619unsafe extern "C" {
631620 // LLVMRustThinLTOData
632621 pub(crate) type ThinLTOData;
......@@ -775,19 +764,12 @@ pub(crate) struct Builder<'a>(InvariantOpaque<'a>);
775764pub(crate) struct PassManager<'a>(InvariantOpaque<'a>);
776765unsafe extern "C" {
777766 pub type TargetMachine;
778 pub(crate) type Archive;
779767}
780#[repr(C)]
781pub(crate) struct ArchiveIterator<'a>(InvariantOpaque<'a>);
782#[repr(C)]
783pub(crate) struct ArchiveChild<'a>(InvariantOpaque<'a>);
784768unsafe extern "C" {
785769 pub(crate) type Twine;
786770 pub(crate) type DiagnosticInfo;
787771 pub(crate) type SMDiagnostic;
788772}
789#[repr(C)]
790pub(crate) struct RustArchiveMember<'a>(InvariantOpaque<'a>);
791773/// Opaque pointee of `LLVMOperandBundleRef`.
792774#[repr(C)]
793775pub(crate) struct OperandBundle<'a>(InvariantOpaque<'a>);
......@@ -2443,7 +2425,7 @@ unsafe extern "C" {
24432425 OutputObjFile: *const c_char,
24442426 DebugInfoCompression: *const c_char,
24452427 UseEmulatedTls: bool,
2446 ArgsCstrBuff: *const c_char,
2428 ArgsCstrBuff: *const c_uchar, // See "PTR_LEN_STR".
24472429 ArgsCstrBuffLen: usize,
24482430 UseWasmEH: bool,
24492431 ) -> *mut TargetMachine;
......@@ -2510,19 +2492,6 @@ unsafe extern "C" {
25102492 pub(crate) fn LLVMRustSetNormalizedTarget(M: &Module, triple: *const c_char);
25112493 pub(crate) fn LLVMRustRunRestrictionPass(M: &Module, syms: *const *const c_char, len: size_t);
25122494
2513 pub(crate) fn LLVMRustOpenArchive(path: *const c_char) -> Option<&'static mut Archive>;
2514 pub(crate) fn LLVMRustArchiveIteratorNew(AR: &Archive) -> &mut ArchiveIterator<'_>;
2515 pub(crate) fn LLVMRustArchiveIteratorNext<'a>(
2516 AIR: &ArchiveIterator<'a>,
2517 ) -> Option<&'a mut ArchiveChild<'a>>;
2518 pub(crate) fn LLVMRustArchiveChildName(
2519 ACR: &ArchiveChild<'_>,
2520 size: &mut size_t,
2521 ) -> *const c_char;
2522 pub(crate) fn LLVMRustArchiveChildFree<'a>(ACR: &'a mut ArchiveChild<'a>);
2523 pub(crate) fn LLVMRustArchiveIteratorFree<'a>(AIR: &'a mut ArchiveIterator<'a>);
2524 pub(crate) fn LLVMRustDestroyArchive(AR: &'static mut Archive);
2525
25262495 pub(crate) fn LLVMRustWriteTwineToString(T: &Twine, s: &RustString);
25272496
25282497 pub(crate) fn LLVMRustUnpackOptimizationDiagnostic<'a>(
......@@ -2560,21 +2529,6 @@ unsafe extern "C" {
25602529 num_ranges: &mut usize,
25612530 ) -> bool;
25622531
2563 pub(crate) fn LLVMRustWriteArchive(
2564 Dst: *const c_char,
2565 NumMembers: size_t,
2566 Members: *const &RustArchiveMember<'_>,
2567 WriteSymbtab: bool,
2568 Kind: ArchiveKind,
2569 isEC: bool,
2570 ) -> LLVMRustResult;
2571 pub(crate) fn LLVMRustArchiveMemberNew<'a>(
2572 Filename: *const c_char,
2573 Name: *const c_char,
2574 Child: Option<&ArchiveChild<'a>>,
2575 ) -> &'a mut RustArchiveMember<'a>;
2576 pub(crate) fn LLVMRustArchiveMemberFree<'a>(Member: &'a mut RustArchiveMember<'a>);
2577
25782532 pub(crate) fn LLVMRustSetDataLayoutFromTargetMachine<'a>(M: &'a Module, TM: &'a TargetMachine);
25792533
25802534 pub(crate) fn LLVMRustPositionBuilderPastAllocas<'a>(B: &Builder<'a>, Fn: &'a Value);
compiler/rustc_codegen_llvm/src/llvm/mod.rs-17
......@@ -3,7 +3,6 @@
33use std::ffi::{CStr, CString};
44use std::num::NonZero;
55use std::ptr;
6use std::str::FromStr;
76use std::string::FromUtf8Error;
87
98use libc::c_uint;
......@@ -16,7 +15,6 @@ pub(crate) use self::MetadataType::*;
1615pub(crate) use self::ffi::*;
1716use crate::common::AsCCharPtr;
1817
19pub(crate) mod archive_ro;
2018pub(crate) mod diagnostic;
2119pub(crate) mod enzyme_ffi;
2220mod ffi;
......@@ -152,21 +150,6 @@ pub(crate) enum CodeGenOptSize {
152150 CodeGenOptSizeAggressive = 2,
153151}
154152
155impl FromStr for ArchiveKind {
156 type Err = ();
157
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 match s {
160 "gnu" => Ok(ArchiveKind::K_GNU),
161 "bsd" => Ok(ArchiveKind::K_BSD),
162 "darwin" => Ok(ArchiveKind::K_DARWIN),
163 "coff" => Ok(ArchiveKind::K_COFF),
164 "aix_big" => Ok(ArchiveKind::K_AIXBIG),
165 _ => Err(()),
166 }
167 }
168}
169
170153pub(crate) fn SetInstructionCallConv(instr: &Value, cc: CallConv) {
171154 unsafe {
172155 LLVMSetInstructionCallConv(instr, cc as c_uint);
compiler/rustc_codegen_ssa/messages.ftl+2-2
......@@ -171,8 +171,8 @@ codegen_ssa_invalid_monomorphization_unsupported_symbol = invalid monomorphizati
171171
172172codegen_ssa_invalid_monomorphization_unsupported_symbol_of_size = invalid monomorphization of `{$name}` intrinsic: unsupported {$symbol} from `{$in_ty}` with element `{$in_elem}` of size `{$size}` to `{$ret_ty}`
173173
174codegen_ssa_invalid_no_sanitize = invalid argument for `no_sanitize`
175 .note = expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
174codegen_ssa_invalid_sanitize = invalid argument for `sanitize`
175 .note = expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
176176
177177codegen_ssa_invalid_windows_subsystem = invalid windows subsystem `{$subsystem}`, only `windows` and `console` are allowed
178178
compiler/rustc_codegen_ssa/src/codegen_attrs.rs+100-36
......@@ -77,32 +77,6 @@ fn parse_instruction_set_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<Instr
7777 }
7878}
7979
80// FIXME(jdonszelmann): remove when no_sanitize becomes a parsed attr
81fn parse_no_sanitize_attr(tcx: TyCtxt<'_>, attr: &Attribute) -> Option<SanitizerSet> {
82 let list = attr.meta_item_list()?;
83 let mut sanitizer_set = SanitizerSet::empty();
84
85 for item in list.iter() {
86 match item.name() {
87 Some(sym::address) => {
88 sanitizer_set |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
89 }
90 Some(sym::cfi) => sanitizer_set |= SanitizerSet::CFI,
91 Some(sym::kcfi) => sanitizer_set |= SanitizerSet::KCFI,
92 Some(sym::memory) => sanitizer_set |= SanitizerSet::MEMORY,
93 Some(sym::memtag) => sanitizer_set |= SanitizerSet::MEMTAG,
94 Some(sym::shadow_call_stack) => sanitizer_set |= SanitizerSet::SHADOWCALLSTACK,
95 Some(sym::thread) => sanitizer_set |= SanitizerSet::THREAD,
96 Some(sym::hwaddress) => sanitizer_set |= SanitizerSet::HWADDRESS,
97 _ => {
98 tcx.dcx().emit_err(errors::InvalidNoSanitize { span: item.span() });
99 }
100 }
101 }
102
103 Some(sanitizer_set)
104}
105
10680// FIXME(jdonszelmann): remove when patchable_function_entry becomes a parsed attr
10781fn parse_patchable_function_entry(
10882 tcx: TyCtxt<'_>,
......@@ -161,7 +135,7 @@ fn parse_patchable_function_entry(
161135#[derive(Default)]
162136struct InterestingAttributeDiagnosticSpans {
163137 link_ordinal: Option<Span>,
164 no_sanitize: Option<Span>,
138 sanitize: Option<Span>,
165139 inline: Option<Span>,
166140 no_mangle: Option<Span>,
167141}
......@@ -330,11 +304,7 @@ fn process_builtin_attrs(
330304 codegen_fn_attrs.flags |= CodegenFnAttrFlags::ALLOCATOR_ZEROED
331305 }
332306 sym::thread_local => codegen_fn_attrs.flags |= CodegenFnAttrFlags::THREAD_LOCAL,
333 sym::no_sanitize => {
334 interesting_spans.no_sanitize = Some(attr.span());
335 codegen_fn_attrs.no_sanitize |=
336 parse_no_sanitize_attr(tcx, attr).unwrap_or_default();
337 }
307 sym::sanitize => interesting_spans.sanitize = Some(attr.span()),
338308 sym::instruction_set => {
339309 codegen_fn_attrs.instruction_set = parse_instruction_set_attr(tcx, attr)
340310 }
......@@ -358,6 +328,8 @@ fn apply_overrides(tcx: TyCtxt<'_>, did: LocalDefId, codegen_fn_attrs: &mut Code
358328 codegen_fn_attrs.alignment =
359329 Ord::max(codegen_fn_attrs.alignment, tcx.sess.opts.unstable_opts.min_function_alignment);
360330
331 // Compute the disabled sanitizers.
332 codegen_fn_attrs.no_sanitize |= tcx.disabled_sanitizers_for(did);
361333 // On trait methods, inherit the `#[align]` of the trait's method prototype.
362334 codegen_fn_attrs.alignment = Ord::max(codegen_fn_attrs.alignment, tcx.inherited_align(did));
363335
......@@ -455,11 +427,11 @@ fn check_result(
455427 if !codegen_fn_attrs.no_sanitize.is_empty()
456428 && codegen_fn_attrs.inline.always()
457429 && let (Some(no_sanitize_span), Some(inline_span)) =
458 (interesting_spans.no_sanitize, interesting_spans.inline)
430 (interesting_spans.sanitize, interesting_spans.inline)
459431 {
460432 let hir_id = tcx.local_def_id_to_hir_id(did);
461433 tcx.node_span_lint(lint::builtin::INLINE_NO_SANITIZE, hir_id, no_sanitize_span, |lint| {
462 lint.primary_message("`no_sanitize` will have no effect after inlining");
434 lint.primary_message("setting `sanitize` off will have no effect after inlining");
463435 lint.span_note(inline_span, "inlining requested here");
464436 })
465437 }
......@@ -585,6 +557,93 @@ fn opt_trait_item(tcx: TyCtxt<'_>, def_id: DefId) -> Option<DefId> {
585557 }
586558}
587559
560/// For an attr that has the `sanitize` attribute, read the list of
561/// disabled sanitizers. `current_attr` holds the information about
562/// previously parsed attributes.
563fn parse_sanitize_attr(
564 tcx: TyCtxt<'_>,
565 attr: &Attribute,
566 current_attr: SanitizerSet,
567) -> SanitizerSet {
568 let mut result = current_attr;
569 if let Some(list) = attr.meta_item_list() {
570 for item in list.iter() {
571 let MetaItemInner::MetaItem(set) = item else {
572 tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
573 break;
574 };
575 let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
576 match segments.as_slice() {
577 // Similar to clang, sanitize(address = ..) and
578 // sanitize(kernel_address = ..) control both ASan and KASan
579 // Source: https://reviews.llvm.org/D44981.
580 [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::off) => {
581 result |= SanitizerSet::ADDRESS | SanitizerSet::KERNELADDRESS
582 }
583 [sym::address] | [sym::kernel_address] if set.value_str() == Some(sym::on) => {
584 result &= !SanitizerSet::ADDRESS;
585 result &= !SanitizerSet::KERNELADDRESS;
586 }
587 [sym::cfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::CFI,
588 [sym::cfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::CFI,
589 [sym::kcfi] if set.value_str() == Some(sym::off) => result |= SanitizerSet::KCFI,
590 [sym::kcfi] if set.value_str() == Some(sym::on) => result &= !SanitizerSet::KCFI,
591 [sym::memory] if set.value_str() == Some(sym::off) => {
592 result |= SanitizerSet::MEMORY
593 }
594 [sym::memory] if set.value_str() == Some(sym::on) => {
595 result &= !SanitizerSet::MEMORY
596 }
597 [sym::memtag] if set.value_str() == Some(sym::off) => {
598 result |= SanitizerSet::MEMTAG
599 }
600 [sym::memtag] if set.value_str() == Some(sym::on) => {
601 result &= !SanitizerSet::MEMTAG
602 }
603 [sym::shadow_call_stack] if set.value_str() == Some(sym::off) => {
604 result |= SanitizerSet::SHADOWCALLSTACK
605 }
606 [sym::shadow_call_stack] if set.value_str() == Some(sym::on) => {
607 result &= !SanitizerSet::SHADOWCALLSTACK
608 }
609 [sym::thread] if set.value_str() == Some(sym::off) => {
610 result |= SanitizerSet::THREAD
611 }
612 [sym::thread] if set.value_str() == Some(sym::on) => {
613 result &= !SanitizerSet::THREAD
614 }
615 [sym::hwaddress] if set.value_str() == Some(sym::off) => {
616 result |= SanitizerSet::HWADDRESS
617 }
618 [sym::hwaddress] if set.value_str() == Some(sym::on) => {
619 result &= !SanitizerSet::HWADDRESS
620 }
621 _ => {
622 tcx.dcx().emit_err(errors::InvalidSanitize { span: attr.span() });
623 }
624 }
625 }
626 }
627 result
628}
629
630fn disabled_sanitizers_for(tcx: TyCtxt<'_>, did: LocalDefId) -> SanitizerSet {
631 // Backtrack to the crate root.
632 let disabled = match tcx.opt_local_parent(did) {
633 // Check the parent (recursively).
634 Some(parent) => tcx.disabled_sanitizers_for(parent),
635 // We reached the crate root without seeing an attribute, so
636 // there is no sanitizers to exclude.
637 None => SanitizerSet::empty(),
638 };
639
640 // Check for a sanitize annotation directly on this def.
641 if let Some(attr) = tcx.get_attr(did, sym::sanitize) {
642 return parse_sanitize_attr(tcx, attr, disabled);
643 }
644 disabled
645}
646
588647/// Checks if the provided DefId is a method in a trait impl for a trait which has track_caller
589648/// applied to the method prototype.
590649fn should_inherit_track_caller(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
......@@ -709,6 +768,11 @@ pub fn autodiff_attrs(tcx: TyCtxt<'_>, id: DefId) -> Option<AutoDiffAttrs> {
709768}
710769
711770pub(crate) fn provide(providers: &mut Providers) {
712 *providers =
713 Providers { codegen_fn_attrs, should_inherit_track_caller, inherited_align, ..*providers };
771 *providers = Providers {
772 codegen_fn_attrs,
773 should_inherit_track_caller,
774 inherited_align,
775 disabled_sanitizers_for,
776 ..*providers
777 };
714778}
compiler/rustc_codegen_ssa/src/errors.rs+2-2
......@@ -1121,9 +1121,9 @@ impl IntoDiagArg for ExpectedPointerMutability {
11211121}
11221122
11231123#[derive(Diagnostic)]
1124#[diag(codegen_ssa_invalid_no_sanitize)]
1124#[diag(codegen_ssa_invalid_sanitize)]
11251125#[note]
1126pub(crate) struct InvalidNoSanitize {
1126pub(crate) struct InvalidSanitize {
11271127 #[primary_span]
11281128 pub span: Span,
11291129}
compiler/rustc_const_eval/src/interpret/operand.rs+10
......@@ -175,6 +175,16 @@ impl<Prov: Provenance> Immediate<Prov> {
175175 }
176176 interp_ok(())
177177 }
178
179 pub fn has_provenance(&self) -> bool {
180 match self {
181 Immediate::Scalar(scalar) => matches!(scalar, Scalar::Ptr { .. }),
182 Immediate::ScalarPair(s1, s2) => {
183 matches!(s1, Scalar::Ptr { .. }) || matches!(s2, Scalar::Ptr { .. })
184 }
185 Immediate::Uninit => false,
186 }
187 }
178188}
179189
180190// ScalarPair needs a type to interpret, so we often have an immediate and a type together
compiler/rustc_const_eval/src/interpret/place.rs+7
......@@ -759,6 +759,13 @@ where
759759 &mut self,
760760 dest: &impl Writeable<'tcx, M::Provenance>,
761761 ) -> InterpResult<'tcx> {
762 // If this is an efficiently represented local variable without provenance, skip the
763 // `as_mplace_or_mutable_local` that would otherwise force this local into memory.
764 if let Right(imm) = dest.to_op(self)?.as_mplace_or_imm() {
765 if !imm.has_provenance() {
766 return interp_ok(());
767 }
768 }
762769 match self.as_mplace_or_mutable_local(&dest.to_place())? {
763770 Right((local_val, _local_layout, local)) => {
764771 local_val.clear_provenance()?;
compiler/rustc_feature/src/builtin_attrs.rs+8-5
......@@ -6,6 +6,7 @@ use AttributeDuplicates::*;
66use AttributeGate::*;
77use AttributeType::*;
88use rustc_data_structures::fx::FxHashMap;
9use rustc_hir::AttrStyle;
910use rustc_hir::attrs::EncodeCrossCrate;
1011use rustc_span::edition::Edition;
1112use rustc_span::{Symbol, sym};
......@@ -132,9 +133,12 @@ pub struct AttributeTemplate {
132133}
133134
134135impl AttributeTemplate {
135 pub fn suggestions(&self, inner: bool, name: impl std::fmt::Display) -> Vec<String> {
136 pub fn suggestions(&self, style: AttrStyle, name: impl std::fmt::Display) -> Vec<String> {
136137 let mut suggestions = vec![];
137 let inner = if inner { "!" } else { "" };
138 let inner = match style {
139 AttrStyle::Outer => "",
140 AttrStyle::Inner => "!",
141 };
138142 if self.word {
139143 suggestions.push(format!("#{inner}[{name}]"));
140144 }
......@@ -741,9 +745,8 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
741745 ErrorPreceding, EncodeCrossCrate::No
742746 ),
743747 gated!(
744 no_sanitize, Normal,
745 template!(List: &["address, kcfi, memory, thread"]), DuplicatesOk,
746 EncodeCrossCrate::No, experimental!(no_sanitize)
748 sanitize, Normal, template!(List: &[r#"address = "on|off""#, r#"kernel_address = "on|off""#, r#"cfi = "on|off""#, r#"hwaddress = "on|off""#, r#"kcfi = "on|off""#, r#"memory = "on|off""#, r#"memtag = "on|off""#, r#"shadow_call_stack = "on|off""#, r#"thread = "on|off""#]), ErrorPreceding,
749 EncodeCrossCrate::No, sanitize, experimental!(sanitize),
747750 ),
748751 gated!(
749752 coverage, Normal, template!(OneOf: &[sym::off, sym::on]),
compiler/rustc_feature/src/removed.rs+3
......@@ -190,6 +190,9 @@ declare_features! (
190190 (removed, no_coverage, "1.74.0", Some(84605), Some("renamed to `coverage_attribute`"), 114656),
191191 /// Allows `#[no_debug]`.
192192 (removed, no_debug, "1.43.0", Some(29721), Some("removed due to lack of demand"), 69667),
193 // Allows the use of `no_sanitize` attribute.
194 /// The feature was renamed to `sanitize` and the attribute to `#[sanitize(xyz = "on|off")]`
195 (removed, no_sanitize, "CURRENT_RUSTC_VERSION", Some(39699), Some(r#"renamed to sanitize(xyz = "on|off")"#), 142681),
193196 /// Note: this feature was previously recorded in a separate
194197 /// `STABLE_REMOVED` list because it, uniquely, was once stable but was
195198 /// then removed. But there was no utility storing it separately, so now
compiler/rustc_feature/src/unstable.rs+2-2
......@@ -594,8 +594,6 @@ declare_features! (
594594 (unstable, new_range, "1.86.0", Some(123741)),
595595 /// Allows `#![no_core]`.
596596 (unstable, no_core, "1.3.0", Some(29639)),
597 /// Allows the use of `no_sanitize` attribute.
598 (unstable, no_sanitize, "1.42.0", Some(39699)),
599597 /// Allows using the `non_exhaustive_omitted_patterns` lint.
600598 (unstable, non_exhaustive_omitted_patterns_lint, "1.57.0", Some(89554)),
601599 /// Allows `for<T>` binders in where-clauses
......@@ -628,6 +626,8 @@ declare_features! (
628626 (unstable, return_type_notation, "1.70.0", Some(109417)),
629627 /// Allows `extern "rust-cold"`.
630628 (unstable, rust_cold_cc, "1.63.0", Some(97544)),
629 /// Allows the use of the `sanitize` attribute.
630 (unstable, sanitize, "CURRENT_RUSTC_VERSION", Some(39699)),
631631 /// Allows the use of SIMD types in functions declared in `extern` blocks.
632632 (unstable, simd_ffi, "1.0.0", Some(27731)),
633633 /// Allows specialization of implementations (RFC 1210).
compiler/rustc_hir_typeck/src/expr.rs+3
......@@ -290,6 +290,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
290290 | ExprKind::Let(..)
291291 | ExprKind::Loop(..)
292292 | ExprKind::Match(..) => {}
293 // Do not warn on `as` casts from never to any,
294 // they are sometimes required to appeal typeck.
295 ExprKind::Cast(_, _) => {}
293296 // If `expr` is a result of desugaring the try block and is an ok-wrapped
294297 // diverging expression (e.g. it arose from desugaring of `try { return }`),
295298 // we skip issuing a warning because it is autogenerated code.
compiler/rustc_lint_defs/src/builtin.rs+6-6
......@@ -2301,18 +2301,18 @@ declare_lint! {
23012301
23022302declare_lint! {
23032303 /// The `inline_no_sanitize` lint detects incompatible use of
2304 /// [`#[inline(always)]`][inline] and [`#[no_sanitize(...)]`][no_sanitize].
2304 /// [`#[inline(always)]`][inline] and [`#[sanitize(xyz = "off")]`][sanitize].
23052305 ///
23062306 /// [inline]: https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute
2307 /// [no_sanitize]: https://doc.rust-lang.org/nightly/unstable-book/language-features/no-sanitize.html
2307 /// [sanitize]: https://doc.rust-lang.org/nightly/unstable-book/language-features/no-sanitize.html
23082308 ///
23092309 /// ### Example
23102310 ///
23112311 /// ```rust
2312 /// #![feature(no_sanitize)]
2312 /// #![feature(sanitize)]
23132313 ///
23142314 /// #[inline(always)]
2315 /// #[no_sanitize(address)]
2315 /// #[sanitize(address = "off")]
23162316 /// fn x() {}
23172317 ///
23182318 /// fn main() {
......@@ -2325,11 +2325,11 @@ declare_lint! {
23252325 /// ### Explanation
23262326 ///
23272327 /// The use of the [`#[inline(always)]`][inline] attribute prevents the
2328 /// the [`#[no_sanitize(...)]`][no_sanitize] attribute from working.
2328 /// the [`#[sanitize(xyz = "off")]`][sanitize] attribute from working.
23292329 /// Consider temporarily removing `inline` attribute.
23302330 pub INLINE_NO_SANITIZE,
23312331 Warn,
2332 "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`",
2332 r#"detects incompatible use of `#[inline(always)]` and `#[sanitize(... = "off")]`"#,
23332333}
23342334
23352335declare_lint! {
compiler/rustc_llvm/build.rs-1
......@@ -226,7 +226,6 @@ fn main() {
226226 rerun_if_changed_anything_in_dir(Path::new("llvm-wrapper"));
227227 cfg.file("llvm-wrapper/PassWrapper.cpp")
228228 .file("llvm-wrapper/RustWrapper.cpp")
229 .file("llvm-wrapper/ArchiveWrapper.cpp")
230229 .file("llvm-wrapper/CoverageMappingWrapper.cpp")
231230 .file("llvm-wrapper/SymbolWrapper.cpp")
232231 .file("llvm-wrapper/Linker.cpp")
compiler/rustc_llvm/llvm-wrapper/ArchiveWrapper.cpp deleted-208
......@@ -1,208 +0,0 @@
1#include "LLVMWrapper.h"
2
3#include "llvm/Object/Archive.h"
4#include "llvm/Object/ArchiveWriter.h"
5#include "llvm/Support/Path.h"
6
7using namespace llvm;
8using namespace llvm::object;
9
10struct RustArchiveMember {
11 const char *Filename;
12 const char *Name;
13 Archive::Child Child;
14
15 RustArchiveMember()
16 : Filename(nullptr), Name(nullptr), Child(nullptr, nullptr, nullptr) {}
17 ~RustArchiveMember() {}
18};
19
20struct RustArchiveIterator {
21 bool First;
22 Archive::child_iterator Cur;
23 Archive::child_iterator End;
24 std::unique_ptr<Error> Err;
25
26 RustArchiveIterator(Archive::child_iterator Cur, Archive::child_iterator End,
27 std::unique_ptr<Error> Err)
28 : First(true), Cur(Cur), End(End), Err(std::move(Err)) {}
29};
30
31enum class LLVMRustArchiveKind {
32 GNU,
33 BSD,
34 DARWIN,
35 COFF,
36 AIX_BIG,
37};
38
39static Archive::Kind fromRust(LLVMRustArchiveKind Kind) {
40 switch (Kind) {
41 case LLVMRustArchiveKind::GNU:
42 return Archive::K_GNU;
43 case LLVMRustArchiveKind::BSD:
44 return Archive::K_BSD;
45 case LLVMRustArchiveKind::DARWIN:
46 return Archive::K_DARWIN;
47 case LLVMRustArchiveKind::COFF:
48 return Archive::K_COFF;
49 case LLVMRustArchiveKind::AIX_BIG:
50 return Archive::K_AIXBIG;
51 default:
52 report_fatal_error("Bad ArchiveKind.");
53 }
54}
55
56typedef OwningBinary<Archive> *LLVMRustArchiveRef;
57typedef RustArchiveMember *LLVMRustArchiveMemberRef;
58typedef Archive::Child *LLVMRustArchiveChildRef;
59typedef Archive::Child const *LLVMRustArchiveChildConstRef;
60typedef RustArchiveIterator *LLVMRustArchiveIteratorRef;
61
62extern "C" LLVMRustArchiveRef LLVMRustOpenArchive(char *Path) {
63 ErrorOr<std::unique_ptr<MemoryBuffer>> BufOr = MemoryBuffer::getFile(
64 Path, /*IsText*/ false, /*RequiresNullTerminator=*/false);
65 if (!BufOr) {
66 LLVMRustSetLastError(BufOr.getError().message().c_str());
67 return nullptr;
68 }
69
70 Expected<std::unique_ptr<Archive>> ArchiveOr =
71 Archive::create(BufOr.get()->getMemBufferRef());
72
73 if (!ArchiveOr) {
74 LLVMRustSetLastError(toString(ArchiveOr.takeError()).c_str());
75 return nullptr;
76 }
77
78 OwningBinary<Archive> *Ret = new OwningBinary<Archive>(
79 std::move(ArchiveOr.get()), std::move(BufOr.get()));
80
81 return Ret;
82}
83
84extern "C" void LLVMRustDestroyArchive(LLVMRustArchiveRef RustArchive) {
85 delete RustArchive;
86}
87
88extern "C" LLVMRustArchiveIteratorRef
89LLVMRustArchiveIteratorNew(LLVMRustArchiveRef RustArchive) {
90 Archive *Archive = RustArchive->getBinary();
91 std::unique_ptr<Error> Err = std::make_unique<Error>(Error::success());
92 auto Cur = Archive->child_begin(*Err);
93 if (*Err) {
94 LLVMRustSetLastError(toString(std::move(*Err)).c_str());
95 return nullptr;
96 }
97 auto End = Archive->child_end();
98 return new RustArchiveIterator(Cur, End, std::move(Err));
99}
100
101extern "C" LLVMRustArchiveChildConstRef
102LLVMRustArchiveIteratorNext(LLVMRustArchiveIteratorRef RAI) {
103 if (RAI->Cur == RAI->End)
104 return nullptr;
105
106 // Advancing the iterator validates the next child, and this can
107 // uncover an error. LLVM requires that we check all Errors,
108 // so we only advance the iterator if we actually need to fetch
109 // the next child.
110 // This means we must not advance the iterator in the *first* call,
111 // but instead advance it *before* fetching the child in all later calls.
112 if (!RAI->First) {
113 ++RAI->Cur;
114 if (*RAI->Err) {
115 LLVMRustSetLastError(toString(std::move(*RAI->Err)).c_str());
116 return nullptr;
117 }
118 } else {
119 RAI->First = false;
120 }
121
122 if (RAI->Cur == RAI->End)
123 return nullptr;
124
125 const Archive::Child &Child = *RAI->Cur.operator->();
126 Archive::Child *Ret = new Archive::Child(Child);
127
128 return Ret;
129}
130
131extern "C" void LLVMRustArchiveChildFree(LLVMRustArchiveChildRef Child) {
132 delete Child;
133}
134
135extern "C" void LLVMRustArchiveIteratorFree(LLVMRustArchiveIteratorRef RAI) {
136 delete RAI;
137}
138
139extern "C" const char *
140LLVMRustArchiveChildName(LLVMRustArchiveChildConstRef Child, size_t *Size) {
141 Expected<StringRef> NameOrErr = Child->getName();
142 if (!NameOrErr) {
143 // rustc_codegen_llvm currently doesn't use this error string, but it might
144 // be useful in the future, and in the meantime this tells LLVM that the
145 // error was not ignored and that it shouldn't abort the process.
146 LLVMRustSetLastError(toString(NameOrErr.takeError()).c_str());
147 return nullptr;
148 }
149 StringRef Name = NameOrErr.get();
150 *Size = Name.size();
151 return Name.data();
152}
153
154extern "C" LLVMRustArchiveMemberRef
155LLVMRustArchiveMemberNew(char *Filename, char *Name,
156 LLVMRustArchiveChildRef Child) {
157 RustArchiveMember *Member = new RustArchiveMember;
158 Member->Filename = Filename;
159 Member->Name = Name;
160 if (Child)
161 Member->Child = *Child;
162 return Member;
163}
164
165extern "C" void LLVMRustArchiveMemberFree(LLVMRustArchiveMemberRef Member) {
166 delete Member;
167}
168
169extern "C" LLVMRustResult LLVMRustWriteArchive(
170 char *Dst, size_t NumMembers, const LLVMRustArchiveMemberRef *NewMembers,
171 bool WriteSymbtab, LLVMRustArchiveKind RustKind, bool isEC) {
172
173 std::vector<NewArchiveMember> Members;
174 auto Kind = fromRust(RustKind);
175
176 for (size_t I = 0; I < NumMembers; I++) {
177 auto Member = NewMembers[I];
178 assert(Member->Name);
179 if (Member->Filename) {
180 Expected<NewArchiveMember> MOrErr =
181 NewArchiveMember::getFile(Member->Filename, true);
182 if (!MOrErr) {
183 LLVMRustSetLastError(toString(MOrErr.takeError()).c_str());
184 return LLVMRustResult::Failure;
185 }
186 MOrErr->MemberName = sys::path::filename(MOrErr->MemberName);
187 Members.push_back(std::move(*MOrErr));
188 } else {
189 Expected<NewArchiveMember> MOrErr =
190 NewArchiveMember::getOldMember(Member->Child, true);
191 if (!MOrErr) {
192 LLVMRustSetLastError(toString(MOrErr.takeError()).c_str());
193 return LLVMRustResult::Failure;
194 }
195 Members.push_back(std::move(*MOrErr));
196 }
197 }
198
199 auto SymtabMode = WriteSymbtab ? SymtabWritingMode::NormalSymtab
200 : SymtabWritingMode::NoSymtab;
201 auto Result =
202 writeArchive(Dst, Members, SymtabMode, Kind, true, false, nullptr, isEC);
203 if (!Result)
204 return LLVMRustResult::Success;
205 LLVMRustSetLastError(toString(std::move(Result)).c_str());
206
207 return LLVMRustResult::Failure;
208}
compiler/rustc_middle/src/middle/codegen_fn_attrs.rs+2-2
......@@ -61,8 +61,8 @@ pub struct CodegenFnAttrs {
6161 /// The `#[link_section = "..."]` attribute, or what executable section this
6262 /// should be placed in.
6363 pub link_section: Option<Symbol>,
64 /// The `#[no_sanitize(...)]` attribute. Indicates sanitizers for which
65 /// instrumentation should be disabled inside the annotated function.
64 /// The `#[sanitize(xyz = "off")]` attribute. Indicates sanitizers for which
65 /// instrumentation should be disabled inside the function.
6666 pub no_sanitize: SanitizerSet,
6767 /// The `#[instruction_set(set)]` attribute. Indicates if the generated code should
6868 /// be generated against a specific instruction set. Only usable on architectures which allow
compiler/rustc_middle/src/query/erase.rs+1
......@@ -343,6 +343,7 @@ trivial! {
343343 rustc_span::Symbol,
344344 rustc_span::Ident,
345345 rustc_target::spec::PanicStrategy,
346 rustc_target::spec::SanitizerSet,
346347 rustc_type_ir::Variance,
347348 u32,
348349 usize,
compiler/rustc_middle/src/query/mod.rs+11-1
......@@ -100,7 +100,7 @@ use rustc_session::lint::LintExpectationId;
100100use rustc_span::def_id::LOCAL_CRATE;
101101use rustc_span::source_map::Spanned;
102102use rustc_span::{DUMMY_SP, Span, Symbol};
103use rustc_target::spec::PanicStrategy;
103use rustc_target::spec::{PanicStrategy, SanitizerSet};
104104use {rustc_abi as abi, rustc_ast as ast, rustc_hir as hir};
105105
106106use crate::infer::canonical::{self, Canonical};
......@@ -2686,6 +2686,16 @@ rustc_queries! {
26862686 desc { |tcx| "looking up anon const kind of `{}`", tcx.def_path_str(def_id) }
26872687 separate_provide_extern
26882688 }
2689
2690 /// Checks for the nearest `#[sanitize(xyz = "off")]` or
2691 /// `#[sanitize(xyz = "on")]` on this def and any enclosing defs, up to the
2692 /// crate root.
2693 ///
2694 /// Returns the set of sanitizers that is explicitly disabled for this def.
2695 query disabled_sanitizers_for(key: LocalDefId) -> SanitizerSet {
2696 desc { |tcx| "checking what set of sanitizers are enabled on `{}`", tcx.def_path_str(key) }
2697 feedable
2698 }
26892699}
26902700
26912701rustc_with_all_queries! { define_callbacks! }
compiler/rustc_passes/messages.ftl+6-4
......@@ -454,10 +454,6 @@ passes_no_main_function =
454454 .teach_note = If you don't know the basics of Rust, you can go look to the Rust Book to get started: https://doc.rust-lang.org/book/
455455 .non_function_main = non-function item at `crate::main` is found
456456
457passes_no_sanitize =
458 `#[no_sanitize({$attr_str})]` should be applied to {$accepted_kind}
459 .label = not {$accepted_kind}
460
461457passes_non_exhaustive_with_default_field_values =
462458 `#[non_exhaustive]` can't be used to annotate items with default field values
463459 .label = this struct has default field values
......@@ -552,6 +548,12 @@ passes_rustc_pub_transparent =
552548 attribute should be applied to `#[repr(transparent)]` types
553549 .label = not a `#[repr(transparent)]` type
554550
551passes_sanitize_attribute_not_allowed =
552 sanitize attribute not allowed here
553 .not_fn_impl_mod = not a function, impl block, or module
554 .no_body = function has no body
555 .help = sanitize attribute can be applied to a function (with body), impl block, or module
556
555557passes_should_be_applied_to_fn =
556558 attribute should be applied to a function definition
557559 .label = {$on_crate ->
compiler/rustc_passes/src/check_attr.rs+32-29
......@@ -260,8 +260,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
260260 [sym::diagnostic, sym::on_unimplemented, ..] => {
261261 self.check_diagnostic_on_unimplemented(attr.span(), hir_id, target)
262262 }
263 [sym::no_sanitize, ..] => {
264 self.check_no_sanitize(attr, span, target)
263 [sym::sanitize, ..] => {
264 self.check_sanitize(attr, span, target)
265265 }
266266 [sym::thread_local, ..] => self.check_thread_local(attr, span, target),
267267 [sym::doc, ..] => self.check_doc_attrs(
......@@ -483,39 +483,43 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
483483 }
484484 }
485485
486 fn check_no_sanitize(&self, attr: &Attribute, span: Span, target: Target) {
486 /// Checks that the `#[sanitize(..)]` attribute is applied to a
487 /// function/closure/method, or to an impl block or module.
488 fn check_sanitize(&self, attr: &Attribute, target_span: Span, target: Target) {
489 let mut not_fn_impl_mod = None;
490 let mut no_body = None;
491
487492 if let Some(list) = attr.meta_item_list() {
488493 for item in list.iter() {
489 let sym = item.name();
490 match sym {
491 Some(s @ sym::address | s @ sym::hwaddress) => {
492 let is_valid =
493 matches!(target, Target::Fn | Target::Method(..) | Target::Static);
494 if !is_valid {
495 self.dcx().emit_err(errors::NoSanitize {
496 attr_span: item.span(),
497 defn_span: span,
498 accepted_kind: "a function or static",
499 attr_str: s.as_str(),
500 });
501 }
494 let MetaItemInner::MetaItem(set) = item else {
495 return;
496 };
497 let segments = set.path.segments.iter().map(|x| x.ident.name).collect::<Vec<_>>();
498 match target {
499 Target::Fn
500 | Target::Closure
501 | Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent)
502 | Target::Impl { .. }
503 | Target::Mod => return,
504 Target::Static if matches!(segments.as_slice(), [sym::address]) => return,
505
506 // These are "functions", but they aren't allowed because they don't
507 // have a body, so the usual explanation would be confusing.
508 Target::Method(MethodKind::Trait { body: false }) | Target::ForeignFn => {
509 no_body = Some(target_span);
502510 }
511
503512 _ => {
504 let is_valid = matches!(target, Target::Fn | Target::Method(..));
505 if !is_valid {
506 self.dcx().emit_err(errors::NoSanitize {
507 attr_span: item.span(),
508 defn_span: span,
509 accepted_kind: "a function",
510 attr_str: &match sym {
511 Some(name) => name.to_string(),
512 None => "...".to_string(),
513 },
514 });
515 }
513 not_fn_impl_mod = Some(target_span);
516514 }
517515 }
518516 }
517 self.dcx().emit_err(errors::SanitizeAttributeNotAllowed {
518 attr_span: attr.span(),
519 not_fn_impl_mod,
520 no_body,
521 help: (),
522 });
519523 }
520524 }
521525
......@@ -562,7 +566,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
562566 }
563567 }
564568 }
565
566569 /// Checks if `#[collapse_debuginfo]` is applied to a macro.
567570 fn check_collapse_debuginfo(&self, attr: &Attribute, span: Span, target: Target) {
568571 match target {
compiler/rustc_passes/src/errors.rs+12-6
......@@ -1489,15 +1489,21 @@ pub(crate) struct AttrCrateLevelOnlySugg {
14891489 pub attr: Span,
14901490}
14911491
1492/// "sanitize attribute not allowed here"
14921493#[derive(Diagnostic)]
1493#[diag(passes_no_sanitize)]
1494pub(crate) struct NoSanitize<'a> {
1494#[diag(passes_sanitize_attribute_not_allowed)]
1495pub(crate) struct SanitizeAttributeNotAllowed {
14951496 #[primary_span]
14961497 pub attr_span: Span,
1497 #[label]
1498 pub defn_span: Span,
1499 pub accepted_kind: &'a str,
1500 pub attr_str: &'a str,
1498 /// "not a function, impl block, or module"
1499 #[label(passes_not_fn_impl_mod)]
1500 pub not_fn_impl_mod: Option<Span>,
1501 /// "function has no body"
1502 #[label(passes_no_body)]
1503 pub no_body: Option<Span>,
1504 /// "sanitize attribute can be applied to a function (with body), impl block, or module"
1505 #[help]
1506 pub help: (),
15011507}
15021508
15031509// FIXME(jdonszelmann): move back to rustc_attr
compiler/rustc_span/src/symbol.rs+1
......@@ -1261,6 +1261,7 @@ symbols! {
12611261 iterator,
12621262 iterator_collect_fn,
12631263 kcfi,
1264 kernel_address,
12641265 keylocker_x86,
12651266 keyword,
12661267 kind,
library/core/src/lib.rs+7
......@@ -226,6 +226,13 @@ pub mod assert_matches {
226226 pub use crate::macros::{assert_matches, debug_assert_matches};
227227}
228228
229#[unstable(feature = "derive_from", issue = "144889")]
230/// Unstable module containing the unstable `From` derive macro.
231pub mod from {
232 #[unstable(feature = "derive_from", issue = "144889")]
233 pub use crate::macros::builtin::From;
234}
235
229236// We don't export this through #[macro_export] for now, to avoid breakage.
230237#[unstable(feature = "autodiff", issue = "124509")]
231238/// Unstable module containing the unstable `autodiff` macro.
library/core/src/net/ip_addr.rs+6-6
......@@ -626,13 +626,13 @@ impl Ipv4Addr {
626626 /// # Examples
627627 ///
628628 /// ```
629 /// #![feature(ip_from)]
630629 /// use std::net::Ipv4Addr;
631630 ///
632631 /// let addr = Ipv4Addr::from_octets([13u8, 12u8, 11u8, 10u8]);
633632 /// assert_eq!(Ipv4Addr::new(13, 12, 11, 10), addr);
634633 /// ```
635 #[unstable(feature = "ip_from", issue = "131360")]
634 #[stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
635 #[rustc_const_stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
636636 #[must_use]
637637 #[inline]
638638 pub const fn from_octets(octets: [u8; 4]) -> Ipv4Addr {
......@@ -1464,7 +1464,6 @@ impl Ipv6Addr {
14641464 /// # Examples
14651465 ///
14661466 /// ```
1467 /// #![feature(ip_from)]
14681467 /// use std::net::Ipv6Addr;
14691468 ///
14701469 /// let addr = Ipv6Addr::from_segments([
......@@ -1479,7 +1478,8 @@ impl Ipv6Addr {
14791478 /// addr
14801479 /// );
14811480 /// ```
1482 #[unstable(feature = "ip_from", issue = "131360")]
1481 #[stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
1482 #[rustc_const_stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
14831483 #[must_use]
14841484 #[inline]
14851485 pub const fn from_segments(segments: [u16; 8]) -> Ipv6Addr {
......@@ -2029,7 +2029,6 @@ impl Ipv6Addr {
20292029 /// # Examples
20302030 ///
20312031 /// ```
2032 /// #![feature(ip_from)]
20332032 /// use std::net::Ipv6Addr;
20342033 ///
20352034 /// let addr = Ipv6Addr::from_octets([
......@@ -2044,7 +2043,8 @@ impl Ipv6Addr {
20442043 /// addr
20452044 /// );
20462045 /// ```
2047 #[unstable(feature = "ip_from", issue = "131360")]
2046 #[stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
2047 #[rustc_const_stable(feature = "ip_from", since = "CURRENT_RUSTC_VERSION")]
20482048 #[must_use]
20492049 #[inline]
20502050 pub const fn from_octets(octets: [u8; 16]) -> Ipv6Addr {
library/core/src/option.rs+6-6
......@@ -2095,9 +2095,9 @@ impl<T> Option<&mut T> {
20952095impl<T, E> Option<Result<T, E>> {
20962096 /// Transposes an `Option` of a [`Result`] into a [`Result`] of an `Option`.
20972097 ///
2098 /// [`None`] will be mapped to <code>[Ok]\([None])</code>.
2099 /// <code>[Some]\([Ok]\(\_))</code> and <code>[Some]\([Err]\(\_))</code> will be mapped to
2100 /// <code>[Ok]\([Some]\(\_))</code> and <code>[Err]\(\_)</code>.
2098 /// <code>[Some]\([Ok]\(\_))</code> is mapped to <code>[Ok]\([Some]\(\_))</code>,
2099 /// <code>[Some]\([Err]\(\_))</code> is mapped to <code>[Err]\(\_)</code>,
2100 /// and [`None`] will be mapped to <code>[Ok]\([None])</code>.
21012101 ///
21022102 /// # Examples
21032103 ///
......@@ -2105,9 +2105,9 @@ impl<T, E> Option<Result<T, E>> {
21052105 /// #[derive(Debug, Eq, PartialEq)]
21062106 /// struct SomeErr;
21072107 ///
2108 /// let x: Result<Option<i32>, SomeErr> = Ok(Some(5));
2109 /// let y: Option<Result<i32, SomeErr>> = Some(Ok(5));
2110 /// assert_eq!(x, y.transpose());
2108 /// let x: Option<Result<i32, SomeErr>> = Some(Ok(5));
2109 /// let y: Result<Option<i32>, SomeErr> = Ok(Some(5));
2110 /// assert_eq!(x.transpose(), y);
21112111 /// ```
21122112 #[inline]
21132113 #[stable(feature = "transpose_result", since = "1.33.0")]
library/core/src/prelude/v1.rs-7
......@@ -117,10 +117,3 @@ pub use crate::macros::builtin::deref;
117117 reason = "`type_alias_impl_trait` has open design concerns"
118118)]
119119pub use crate::macros::builtin::define_opaque;
120
121#[unstable(
122 feature = "derive_from",
123 issue = "144889",
124 reason = "`derive(From)` is unstable"
125)]
126pub use crate::macros::builtin::From;
library/coretests/tests/lib.rs-1
......@@ -56,7 +56,6 @@
5656#![feature(hashmap_internals)]
5757#![feature(int_roundings)]
5858#![feature(ip)]
59#![feature(ip_from)]
6059#![feature(is_ascii_octdigit)]
6160#![feature(isolate_most_least_significant_one)]
6261#![feature(iter_advance_by)]
library/std/src/lib.rs+8
......@@ -738,6 +738,14 @@ pub use core::{
738738 unreachable, write, writeln,
739739};
740740
741// Re-export unstable derive macro defined through core.
742#[unstable(feature = "derive_from", issue = "144889")]
743/// Unstable module containing the unstable `From` derive macro.
744pub mod from {
745 #[unstable(feature = "derive_from", issue = "144889")]
746 pub use core::from::From;
747}
748
741749// Include a number of private modules that exist solely to provide
742750// the rustdoc documentation for primitive types. Using `include!`
743751// because rustdoc only looks for these modules at the crate level.
library/std/src/path.rs+65
......@@ -2105,6 +2105,38 @@ impl PartialEq for PathBuf {
21052105 }
21062106}
21072107
2108#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
2109impl cmp::PartialEq<str> for PathBuf {
2110 #[inline]
2111 fn eq(&self, other: &str) -> bool {
2112 &*self == other
2113 }
2114}
2115
2116#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
2117impl cmp::PartialEq<PathBuf> for str {
2118 #[inline]
2119 fn eq(&self, other: &PathBuf) -> bool {
2120 other == self
2121 }
2122}
2123
2124#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
2125impl cmp::PartialEq<String> for PathBuf {
2126 #[inline]
2127 fn eq(&self, other: &String) -> bool {
2128 **self == **other
2129 }
2130}
2131
2132#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
2133impl cmp::PartialEq<PathBuf> for String {
2134 #[inline]
2135 fn eq(&self, other: &PathBuf) -> bool {
2136 other == self
2137 }
2138}
2139
21082140#[stable(feature = "rust1", since = "1.0.0")]
21092141impl Hash for PathBuf {
21102142 fn hash<H: Hasher>(&self, h: &mut H) {
......@@ -3366,6 +3398,39 @@ impl PartialEq for Path {
33663398 }
33673399}
33683400
3401#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
3402impl cmp::PartialEq<str> for Path {
3403 #[inline]
3404 fn eq(&self, other: &str) -> bool {
3405 let other: &OsStr = other.as_ref();
3406 self == other
3407 }
3408}
3409
3410#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
3411impl cmp::PartialEq<Path> for str {
3412 #[inline]
3413 fn eq(&self, other: &Path) -> bool {
3414 other == self
3415 }
3416}
3417
3418#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
3419impl cmp::PartialEq<String> for Path {
3420 #[inline]
3421 fn eq(&self, other: &String) -> bool {
3422 self == &*other
3423 }
3424}
3425
3426#[stable(feature = "eq_str_for_path", since = "CURRENT_RUSTC_VERSION")]
3427impl cmp::PartialEq<Path> for String {
3428 #[inline]
3429 fn eq(&self, other: &Path) -> bool {
3430 other == self
3431 }
3432}
3433
33693434#[stable(feature = "rust1", since = "1.0.0")]
33703435impl Hash for Path {
33713436 fn hash<H: Hasher>(&self, h: &mut H) {
library/std/tests/thread.rs+1
......@@ -19,6 +19,7 @@ fn sleep_very_long() {
1919}
2020
2121#[test]
22#[cfg_attr(target_env = "sgx", ignore = "Time within SGX enclave cannot be trusted")]
2223fn sleep_until() {
2324 let now = Instant::now();
2425 let period = Duration::from_millis(100);
src/bootstrap/src/bin/rustc.rs+10
......@@ -179,6 +179,16 @@ fn main() {
179179 }
180180 }
181181
182 // Here we pass additional paths that essentially act as a sysroot.
183 // These are used to load rustc crates (e.g. `extern crate rustc_ast;`)
184 // for rustc_private tools, so that we do not have to copy them into the
185 // actual sysroot of the compiler that builds the tool.
186 if let Ok(dirs) = env::var("RUSTC_ADDITIONAL_SYSROOT_PATHS") {
187 for dir in dirs.split(",") {
188 cmd.arg(format!("-L{dir}"));
189 }
190 }
191
182192 // Force all crates compiled by this compiler to (a) be unstable and (b)
183193 // allow the `rustc_private` feature to link to other unstable crates
184194 // also in the sysroot. We also do this for host crates, since those
src/bootstrap/src/core/build_steps/check.rs+277-75
......@@ -1,5 +1,8 @@
11//! Implementation of compiling the compiler and standard library, in "check"-based modes.
22
3use std::fs;
4use std::path::{Path, PathBuf};
5
36use crate::core::build_steps::compile::{
47 add_to_sysroot, run_cargo, rustc_cargo, rustc_cargo_env, std_cargo, std_crates_for_run_make,
58};
......@@ -9,11 +12,11 @@ use crate::core::build_steps::tool::{
912 prepare_tool_cargo,
1013};
1114use crate::core::builder::{
12 self, Alias, Builder, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
15 self, Alias, Builder, Cargo, Kind, RunConfig, ShouldRun, Step, StepMetadata, crate_description,
1316};
1417use crate::core::config::TargetSelection;
1518use crate::utils::build_stamp::{self, BuildStamp};
16use crate::{CodegenBackendKind, Compiler, Mode, Subcommand};
19use crate::{CodegenBackendKind, Compiler, Mode, Subcommand, t};
1720
1821#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1922pub struct Std {
......@@ -33,7 +36,7 @@ impl Std {
3336}
3437
3538impl Step for Std {
36 type Output = ();
39 type Output = BuildStamp;
3740 const DEFAULT: bool = true;
3841
3942 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
......@@ -60,13 +63,14 @@ impl Step for Std {
6063
6164 let crates = std_crates_for_run_make(&run);
6265 run.builder.ensure(Std {
63 build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Std),
66 build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Std)
67 .build_compiler(),
6468 target: run.target,
6569 crates,
6670 });
6771 }
6872
69 fn run(self, builder: &Builder<'_>) {
73 fn run(self, builder: &Builder<'_>) -> Self::Output {
7074 let build_compiler = self.build_compiler;
7175 let target = self.target;
7276
......@@ -93,18 +97,27 @@ impl Step for Std {
9397 Kind::Check,
9498 format_args!("library artifacts{}", crate_description(&self.crates)),
9599 Mode::Std,
96 self.build_compiler,
100 build_compiler,
97101 target,
98102 );
99103
100 let stamp = build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check");
101 run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
104 let check_stamp =
105 build_stamp::libstd_stamp(builder, build_compiler, target).with_prefix("check");
106 run_cargo(
107 builder,
108 cargo,
109 builder.config.free_args.clone(),
110 &check_stamp,
111 vec![],
112 true,
113 false,
114 );
102115
103116 drop(_guard);
104117
105118 // don't check test dependencies if we haven't built libtest
106119 if !self.crates.iter().any(|krate| krate == "test") {
107 return;
120 return check_stamp;
108121 }
109122
110123 // Then run cargo again, once we've put the rmeta files for the library
......@@ -137,10 +150,11 @@ impl Step for Std {
137150 Kind::Check,
138151 "library test/bench/example targets",
139152 Mode::Std,
140 self.build_compiler,
153 build_compiler,
141154 target,
142155 );
143156 run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
157 check_stamp
144158 }
145159
146160 fn metadata(&self) -> Option<StepMetadata> {
......@@ -148,12 +162,135 @@ impl Step for Std {
148162 }
149163}
150164
151/// Checks rustc using `build_compiler` and copies the built
152/// .rmeta files into the sysroot of `build_compiler`.
165/// Represents a proof that rustc was **checked**.
166/// Contains directories with .rmeta files generated by checking rustc for a specific
167/// target.
168#[derive(Debug, Clone, PartialEq, Eq, Hash)]
169struct RmetaSysroot {
170 host_dir: PathBuf,
171 target_dir: PathBuf,
172}
173
174impl RmetaSysroot {
175 /// Copy rmeta artifacts from the given `stamp` into a sysroot located at `directory`.
176 fn from_stamp(
177 builder: &Builder<'_>,
178 stamp: BuildStamp,
179 target: TargetSelection,
180 directory: &Path,
181 ) -> Self {
182 let host_dir = directory.join("host");
183 let target_dir = directory.join(target);
184 let _ = fs::remove_dir_all(directory);
185 t!(fs::create_dir_all(directory));
186 add_to_sysroot(builder, &target_dir, &host_dir, &stamp);
187
188 Self { host_dir, target_dir }
189 }
190
191 /// Configure the given cargo invocation so that the compiled crate will be able to use
192 /// rustc .rmeta artifacts that were previously generated.
193 fn configure_cargo(&self, cargo: &mut Cargo) {
194 cargo.append_to_env(
195 "RUSTC_ADDITIONAL_SYSROOT_PATHS",
196 format!("{},{}", self.host_dir.to_str().unwrap(), self.target_dir.to_str().unwrap()),
197 ",",
198 );
199 }
200}
201
202/// Checks rustc using the given `build_compiler` for the given `target`, and produces
203/// a sysroot in the build directory that stores the generated .rmeta files.
204///
205/// This step exists so that we can store the generated .rmeta artifacts into a separate
206/// directory, instead of copying them into the sysroot of `build_compiler`, which would
207/// "pollute" it (that is especially problematic for the external stage0 rustc).
208#[derive(Debug, Clone, PartialEq, Eq, Hash)]
209struct PrepareRustcRmetaSysroot {
210 build_compiler: CompilerForCheck,
211 target: TargetSelection,
212}
213
214impl PrepareRustcRmetaSysroot {
215 fn new(build_compiler: CompilerForCheck, target: TargetSelection) -> Self {
216 Self { build_compiler, target }
217 }
218}
219
220impl Step for PrepareRustcRmetaSysroot {
221 type Output = RmetaSysroot;
222
223 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
224 run.never()
225 }
226
227 fn run(self, builder: &Builder<'_>) -> Self::Output {
228 // Check rustc
229 let stamp = builder.ensure(Rustc::from_build_compiler(
230 self.build_compiler.clone(),
231 self.target,
232 vec![],
233 ));
234
235 let build_compiler = self.build_compiler.build_compiler();
236
237 // Copy the generated rmeta artifacts to a separate directory
238 let dir = builder
239 .out
240 .join(build_compiler.host)
241 .join(format!("stage{}-rustc-rmeta-artifacts", build_compiler.stage + 1));
242 RmetaSysroot::from_stamp(builder, stamp, self.target, &dir)
243 }
244}
245
246/// Checks std using the given `build_compiler` for the given `target`, and produces
247/// a sysroot in the build directory that stores the generated .rmeta files.
248///
249/// This step exists so that we can store the generated .rmeta artifacts into a separate
250/// directory, instead of copying them into the sysroot of `build_compiler`, which would
251/// "pollute" it (that is especially problematic for the external stage0 rustc).
252#[derive(Debug, Clone, PartialEq, Eq, Hash)]
253struct PrepareStdRmetaSysroot {
254 build_compiler: Compiler,
255 target: TargetSelection,
256}
257
258impl PrepareStdRmetaSysroot {
259 fn new(build_compiler: Compiler, target: TargetSelection) -> Self {
260 Self { build_compiler, target }
261 }
262}
263
264impl Step for PrepareStdRmetaSysroot {
265 type Output = RmetaSysroot;
266
267 fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> {
268 run.never()
269 }
270
271 fn run(self, builder: &Builder<'_>) -> Self::Output {
272 // Check std
273 let stamp = builder.ensure(Std {
274 build_compiler: self.build_compiler,
275 target: self.target,
276 crates: vec![],
277 });
278
279 // Copy the generated rmeta artifacts to a separate directory
280 let dir = builder
281 .out
282 .join(self.build_compiler.host)
283 .join(format!("stage{}-std-rmeta-artifacts", self.build_compiler.stage));
284
285 RmetaSysroot::from_stamp(builder, stamp, self.target, &dir)
286 }
287}
288
289/// Checks rustc using `build_compiler`.
153290#[derive(Debug, Clone, PartialEq, Eq, Hash)]
154291pub struct Rustc {
155292 /// Compiler that will check this rustc.
156 pub build_compiler: Compiler,
293 pub build_compiler: CompilerForCheck,
157294 pub target: TargetSelection,
158295 /// Whether to build only a subset of crates.
159296 ///
......@@ -166,12 +303,20 @@ pub struct Rustc {
166303impl Rustc {
167304 pub fn new(builder: &Builder<'_>, target: TargetSelection, crates: Vec<String>) -> Self {
168305 let build_compiler = prepare_compiler_for_check(builder, target, Mode::Rustc);
306 Self::from_build_compiler(build_compiler, target, crates)
307 }
308
309 fn from_build_compiler(
310 build_compiler: CompilerForCheck,
311 target: TargetSelection,
312 crates: Vec<String>,
313 ) -> Self {
169314 Self { build_compiler, target, crates }
170315 }
171316}
172317
173318impl Step for Rustc {
174 type Output = ();
319 type Output = BuildStamp;
175320 const IS_HOST: bool = true;
176321 const DEFAULT: bool = true;
177322
......@@ -191,8 +336,8 @@ impl Step for Rustc {
191336 /// created will also be linked into the sysroot directory.
192337 ///
193338 /// If we check a stage 2 compiler, we will have to first build a stage 1 compiler to check it.
194 fn run(self, builder: &Builder<'_>) {
195 let build_compiler = self.build_compiler;
339 fn run(self, builder: &Builder<'_>) -> Self::Output {
340 let build_compiler = self.build_compiler.build_compiler;
196341 let target = self.target;
197342
198343 let mut cargo = builder::Cargo::new(
......@@ -205,6 +350,7 @@ impl Step for Rustc {
205350 );
206351
207352 rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
353 self.build_compiler.configure_cargo(&mut cargo);
208354
209355 // Explicitly pass -p for all compiler crates -- this will force cargo
210356 // to also check the tests/benches/examples for these crates, rather
......@@ -217,7 +363,7 @@ impl Step for Rustc {
217363 Kind::Check,
218364 format_args!("compiler artifacts{}", crate_description(&self.crates)),
219365 Mode::Rustc,
220 self.build_compiler,
366 self.build_compiler.build_compiler(),
221367 target,
222368 );
223369
......@@ -226,13 +372,12 @@ impl Step for Rustc {
226372
227373 run_cargo(builder, cargo, builder.config.free_args.clone(), &stamp, vec![], true, false);
228374
229 let libdir = builder.sysroot_target_libdir(build_compiler, target);
230 let hostdir = builder.sysroot_target_libdir(build_compiler, build_compiler.host);
231 add_to_sysroot(builder, &libdir, &hostdir, &stamp);
375 stamp
232376 }
233377
234378 fn metadata(&self) -> Option<StepMetadata> {
235 let metadata = StepMetadata::check("rustc", self.target).built_by(self.build_compiler);
379 let metadata = StepMetadata::check("rustc", self.target)
380 .built_by(self.build_compiler.build_compiler());
236381 let metadata = if self.crates.is_empty() {
237382 metadata
238383 } else {
......@@ -242,45 +387,101 @@ impl Step for Rustc {
242387 }
243388}
244389
390/// Represents a compiler that can check something.
391///
392/// If the compiler was created for `Mode::ToolRustc` or `Mode::Codegen`, it will also contain
393/// .rmeta artifacts from rustc that was already checked using `build_compiler`.
394///
395/// All steps that use this struct in a "general way" (i.e. they don't know exactly what kind of
396/// thing is being built) should call `configure_cargo` to ensure that the rmeta artifacts are
397/// properly linked, if present.
398#[derive(Debug, Clone, PartialEq, Eq, Hash)]
399pub struct CompilerForCheck {
400 build_compiler: Compiler,
401 rustc_rmeta_sysroot: Option<RmetaSysroot>,
402 std_rmeta_sysroot: Option<RmetaSysroot>,
403}
404
405impl CompilerForCheck {
406 pub fn build_compiler(&self) -> Compiler {
407 self.build_compiler
408 }
409
410 /// If there are any rustc rmeta artifacts available, configure the Cargo invocation
411 /// so that the artifact being built can find them.
412 pub fn configure_cargo(&self, cargo: &mut Cargo) {
413 if let Some(sysroot) = &self.rustc_rmeta_sysroot {
414 sysroot.configure_cargo(cargo);
415 }
416 if let Some(sysroot) = &self.std_rmeta_sysroot {
417 sysroot.configure_cargo(cargo);
418 }
419 }
420}
421
422/// Prepare the standard library for checking something (that requires stdlib) using
423/// `build_compiler`.
424fn prepare_std(
425 builder: &Builder<'_>,
426 build_compiler: Compiler,
427 target: TargetSelection,
428) -> Option<RmetaSysroot> {
429 // We need to build the host stdlib even if we only check, to compile build scripts and proc
430 // macros
431 builder.std(build_compiler, builder.host_target);
432
433 // If we're cross-compiling, we generate the rmeta files for the given target
434 // This check has to be here, because if we generate both .so and .rmeta files, rustc will fail,
435 // as it will have multiple candidates for linking.
436 if builder.host_target != target {
437 Some(builder.ensure(PrepareStdRmetaSysroot::new(build_compiler, target)))
438 } else {
439 None
440 }
441}
442
245443/// Prepares a compiler that will check something with the given `mode`.
246444pub fn prepare_compiler_for_check(
247445 builder: &Builder<'_>,
248446 target: TargetSelection,
249447 mode: Mode,
250) -> Compiler {
448) -> CompilerForCheck {
251449 let host = builder.host_target;
252450
253 match mode {
451 let mut rustc_rmeta_sysroot = None;
452 let mut std_rmeta_sysroot = None;
453 let build_compiler = match mode {
254454 Mode::ToolBootstrap => builder.compiler(0, host),
455 // We could also only check std here and use `prepare_std`, but `ToolTarget` is currently
456 // only used for running in-tree Clippy on bootstrap tools, so it does not seem worth it to
457 // optimize it. Therefore, here we build std for the target, instead of just checking it.
255458 Mode::ToolTarget => get_tool_target_compiler(builder, ToolTargetBuildMode::Build(target)),
256459 Mode::ToolStd => {
257460 if builder.config.compile_time_deps {
258461 // When --compile-time-deps is passed, we can't use any rustc
259462 // other than the bootstrap compiler. Luckily build scripts and
260463 // proc macros for tools are unlikely to need nightly.
261 return builder.compiler(0, host);
464 builder.compiler(0, host)
465 } else {
466 // These tools require the local standard library to be checked
467 let build_compiler = builder.compiler(builder.top_stage, host);
468 std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
469 build_compiler
262470 }
263
264 // These tools require the local standard library to be checked
265 let build_compiler = builder.compiler(builder.top_stage, host);
266
267 // We need to build the host stdlib to check the tool itself.
268 // We need to build the target stdlib so that the tool can link to it.
269 builder.std(build_compiler, host);
270 // We could only check this library in theory, but `check::Std` doesn't copy rmetas
271 // into `build_compiler`'s sysroot to avoid clashes with `.rlibs`, so we build it
272 // instead.
273 builder.std(build_compiler, target);
274 build_compiler
275471 }
276472 Mode::ToolRustc | Mode::Codegen => {
277473 // Check Rustc to produce the required rmeta artifacts for rustc_private, and then
278474 // return the build compiler that was used to check rustc.
279475 // We do not need to check examples/tests/etc. of Rustc for rustc_private, so we pass
280476 // an empty set of crates, which will avoid using `cargo -p`.
281 let check = Rustc::new(builder, target, vec![]);
282 let build_compiler = check.build_compiler;
283 builder.ensure(check);
477 let compiler_for_rustc = prepare_compiler_for_check(builder, target, Mode::Rustc);
478 rustc_rmeta_sysroot = Some(
479 builder.ensure(PrepareRustcRmetaSysroot::new(compiler_for_rustc.clone(), target)),
480 );
481 let build_compiler = compiler_for_rustc.build_compiler();
482
483 // To check a rustc_private tool, we also need to check std that it will link to
484 std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
284485 build_compiler
285486 }
286487 Mode::Rustc => {
......@@ -294,15 +495,8 @@ pub fn prepare_compiler_for_check(
294495 let stage = if host == target { builder.top_stage - 1 } else { builder.top_stage };
295496 let build_compiler = builder.compiler(stage, host);
296497
297 // Build host std for compiling build scripts
298 builder.std(build_compiler, build_compiler.host);
299
300 // Build target std so that the checked rustc can link to it during the check
301 // FIXME: maybe we can a way to only do a check of std here?
302 // But for that we would have to copy the stdlib rmetas to the sysroot of the build
303 // compiler, which conflicts with std rlibs, if we also build std.
304 builder.std(build_compiler, target);
305
498 // To check rustc, we need to check std that it will link to
499 std_rmeta_sysroot = prepare_std(builder, build_compiler, target);
306500 build_compiler
307501 }
308502 Mode::Std => {
......@@ -311,13 +505,14 @@ pub fn prepare_compiler_for_check(
311505 // stage 0 stdlib is used to compile build scripts and proc macros.
312506 builder.compiler(builder.top_stage, host)
313507 }
314 }
508 };
509 CompilerForCheck { build_compiler, rustc_rmeta_sysroot, std_rmeta_sysroot }
315510}
316511
317512/// Check the Cranelift codegen backend.
318513#[derive(Debug, Clone, PartialEq, Eq, Hash)]
319514pub struct CraneliftCodegenBackend {
320 build_compiler: Compiler,
515 build_compiler: CompilerForCheck,
321516 target: TargetSelection,
322517}
323518
......@@ -332,12 +527,14 @@ impl Step for CraneliftCodegenBackend {
332527 }
333528
334529 fn make_run(run: RunConfig<'_>) {
335 let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
336 run.builder.ensure(CraneliftCodegenBackend { build_compiler, target: run.target });
530 run.builder.ensure(CraneliftCodegenBackend {
531 build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Codegen),
532 target: run.target,
533 });
337534 }
338535
339536 fn run(self, builder: &Builder<'_>) {
340 let build_compiler = self.build_compiler;
537 let build_compiler = self.build_compiler.build_compiler();
341538 let target = self.target;
342539
343540 let mut cargo = builder::Cargo::new(
......@@ -353,12 +550,13 @@ impl Step for CraneliftCodegenBackend {
353550 .arg("--manifest-path")
354551 .arg(builder.src.join("compiler/rustc_codegen_cranelift/Cargo.toml"));
355552 rustc_cargo_env(builder, &mut cargo, target);
553 self.build_compiler.configure_cargo(&mut cargo);
356554
357555 let _guard = builder.msg(
358556 Kind::Check,
359557 "rustc_codegen_cranelift",
360558 Mode::Codegen,
361 self.build_compiler,
559 build_compiler,
362560 target,
363561 );
364562
......@@ -376,7 +574,7 @@ impl Step for CraneliftCodegenBackend {
376574 fn metadata(&self) -> Option<StepMetadata> {
377575 Some(
378576 StepMetadata::check("rustc_codegen_cranelift", self.target)
379 .built_by(self.build_compiler),
577 .built_by(self.build_compiler.build_compiler()),
380578 )
381579 }
382580}
......@@ -384,7 +582,7 @@ impl Step for CraneliftCodegenBackend {
384582/// Check the GCC codegen backend.
385583#[derive(Debug, Clone, PartialEq, Eq, Hash)]
386584pub struct GccCodegenBackend {
387 build_compiler: Compiler,
585 build_compiler: CompilerForCheck,
388586 target: TargetSelection,
389587}
390588
......@@ -399,8 +597,10 @@ impl Step for GccCodegenBackend {
399597 }
400598
401599 fn make_run(run: RunConfig<'_>) {
402 let build_compiler = prepare_compiler_for_check(run.builder, run.target, Mode::Codegen);
403 run.builder.ensure(GccCodegenBackend { build_compiler, target: run.target });
600 run.builder.ensure(GccCodegenBackend {
601 build_compiler: prepare_compiler_for_check(run.builder, run.target, Mode::Codegen),
602 target: run.target,
603 });
404604 }
405605
406606 fn run(self, builder: &Builder<'_>) {
......@@ -410,7 +610,7 @@ impl Step for GccCodegenBackend {
410610 return;
411611 }
412612
413 let build_compiler = self.build_compiler;
613 let build_compiler = self.build_compiler.build_compiler();
414614 let target = self.target;
415615
416616 let mut cargo = builder::Cargo::new(
......@@ -424,14 +624,10 @@ impl Step for GccCodegenBackend {
424624
425625 cargo.arg("--manifest-path").arg(builder.src.join("compiler/rustc_codegen_gcc/Cargo.toml"));
426626 rustc_cargo_env(builder, &mut cargo, target);
627 self.build_compiler.configure_cargo(&mut cargo);
427628
428 let _guard = builder.msg(
429 Kind::Check,
430 "rustc_codegen_gcc",
431 Mode::Codegen,
432 self.build_compiler,
433 target,
434 );
629 let _guard =
630 builder.msg(Kind::Check, "rustc_codegen_gcc", Mode::Codegen, build_compiler, target);
435631
436632 let stamp = build_stamp::codegen_backend_stamp(
437633 builder,
......@@ -445,7 +641,10 @@ impl Step for GccCodegenBackend {
445641 }
446642
447643 fn metadata(&self) -> Option<StepMetadata> {
448 Some(StepMetadata::check("rustc_codegen_gcc", self.target).built_by(self.build_compiler))
644 Some(
645 StepMetadata::check("rustc_codegen_gcc", self.target)
646 .built_by(self.build_compiler.build_compiler()),
647 )
449648 }
450649}
451650
......@@ -467,8 +666,8 @@ macro_rules! tool_check_step {
467666 ) => {
468667 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
469668 pub struct $name {
470 pub build_compiler: Compiler,
471 pub target: TargetSelection,
669 compiler: CompilerForCheck,
670 target: TargetSelection,
472671 }
473672
474673 impl Step for $name {
......@@ -486,7 +685,7 @@ macro_rules! tool_check_step {
486685 let builder = run.builder;
487686 let mode = $mode(builder);
488687
489 let build_compiler = prepare_compiler_for_check(run.builder, target, mode);
688 let compiler = prepare_compiler_for_check(run.builder, target, mode);
490689
491690 // It doesn't make sense to cross-check bootstrap tools
492691 if mode == Mode::ToolBootstrap && target != run.builder.host_target {
......@@ -494,11 +693,11 @@ macro_rules! tool_check_step {
494693 return;
495694 };
496695
497 run.builder.ensure($name { target, build_compiler });
696 run.builder.ensure($name { target, compiler });
498697 }
499698
500699 fn run(self, builder: &Builder<'_>) {
501 let Self { target, build_compiler } = self;
700 let Self { target, compiler } = self;
502701 let allow_features = {
503702 let mut _value = "";
504703 $( _value = $allow_features; )?
......@@ -506,11 +705,11 @@ macro_rules! tool_check_step {
506705 };
507706 let extra_features: &[&str] = &[$($($enable_features),*)?];
508707 let mode = $mode(builder);
509 run_tool_check_step(builder, build_compiler, target, $path, mode, allow_features, extra_features);
708 run_tool_check_step(builder, compiler, target, $path, mode, allow_features, extra_features);
510709 }
511710
512711 fn metadata(&self) -> Option<StepMetadata> {
513 Some(StepMetadata::check(stringify!($name), self.target).built_by(self.build_compiler))
712 Some(StepMetadata::check(stringify!($name), self.target).built_by(self.compiler.build_compiler))
514713 }
515714 }
516715 }
......@@ -519,7 +718,7 @@ macro_rules! tool_check_step {
519718/// Used by the implementation of `Step::run` in `tool_check_step!`.
520719fn run_tool_check_step(
521720 builder: &Builder<'_>,
522 build_compiler: Compiler,
721 compiler: CompilerForCheck,
523722 target: TargetSelection,
524723 path: &str,
525724 mode: Mode,
......@@ -528,6 +727,8 @@ fn run_tool_check_step(
528727) {
529728 let display_name = path.rsplit('/').next().unwrap();
530729
730 let build_compiler = compiler.build_compiler();
731
531732 let extra_features = extra_features.iter().map(|f| f.to_string()).collect::<Vec<String>>();
532733 let mut cargo = prepare_tool_cargo(
533734 builder,
......@@ -544,6 +745,7 @@ fn run_tool_check_step(
544745 &extra_features,
545746 );
546747 cargo.allow_features(allow_features);
748 compiler.configure_cargo(&mut cargo);
547749
548750 // FIXME: check bootstrap doesn't currently work when multiple targets are checked
549751 // FIXME: rust-analyzer does not work with --all-targets
src/bootstrap/src/core/build_steps/clippy.rs+21-12
......@@ -18,7 +18,7 @@ use build_helper::exit;
1818use super::compile::{run_cargo, rustc_cargo, std_cargo};
1919use super::tool::{SourceType, prepare_tool_cargo};
2020use crate::builder::{Builder, ShouldRun};
21use crate::core::build_steps::check::prepare_compiler_for_check;
21use crate::core::build_steps::check::{CompilerForCheck, prepare_compiler_for_check};
2222use crate::core::build_steps::compile::std_crates_for_run_make;
2323use crate::core::builder;
2424use crate::core::builder::{Alias, Kind, RunConfig, Step, StepMetadata, crate_description};
......@@ -231,7 +231,7 @@ impl Step for Std {
231231/// in-tree rustc.
232232#[derive(Debug, Clone, PartialEq, Eq, Hash)]
233233pub struct Rustc {
234 build_compiler: Compiler,
234 build_compiler: CompilerForCheck,
235235 target: TargetSelection,
236236 config: LintConfig,
237237 /// Whether to lint only a subset of crates.
......@@ -271,7 +271,7 @@ impl Step for Rustc {
271271 }
272272
273273 fn run(self, builder: &Builder<'_>) {
274 let build_compiler = self.build_compiler;
274 let build_compiler = self.build_compiler.build_compiler();
275275 let target = self.target;
276276
277277 let mut cargo = builder::Cargo::new(
......@@ -284,6 +284,7 @@ impl Step for Rustc {
284284 );
285285
286286 rustc_cargo(builder, &mut cargo, target, &build_compiler, &self.crates);
287 self.build_compiler.configure_cargo(&mut cargo);
287288
288289 // Explicitly pass -p for all compiler crates -- this will force cargo
289290 // to also lint the tests/benches/examples for these crates, rather
......@@ -312,13 +313,16 @@ impl Step for Rustc {
312313 }
313314
314315 fn metadata(&self) -> Option<StepMetadata> {
315 Some(StepMetadata::clippy("rustc", self.target).built_by(self.build_compiler))
316 Some(
317 StepMetadata::clippy("rustc", self.target)
318 .built_by(self.build_compiler.build_compiler()),
319 )
316320 }
317321}
318322
319323#[derive(Debug, Clone, Hash, PartialEq, Eq)]
320324pub struct CodegenGcc {
321 build_compiler: Compiler,
325 build_compiler: CompilerForCheck,
322326 target: TargetSelection,
323327 config: LintConfig,
324328}
......@@ -347,10 +351,10 @@ impl Step for CodegenGcc {
347351 }
348352
349353 fn run(self, builder: &Builder<'_>) -> Self::Output {
350 let build_compiler = self.build_compiler;
354 let build_compiler = self.build_compiler.build_compiler();
351355 let target = self.target;
352356
353 let cargo = prepare_tool_cargo(
357 let mut cargo = prepare_tool_cargo(
354358 builder,
355359 build_compiler,
356360 Mode::Codegen,
......@@ -360,6 +364,7 @@ impl Step for CodegenGcc {
360364 SourceType::InTree,
361365 &[],
362366 );
367 self.build_compiler.configure_cargo(&mut cargo);
363368
364369 let _guard =
365370 builder.msg(Kind::Clippy, "rustc_codegen_gcc", Mode::ToolRustc, build_compiler, target);
......@@ -379,7 +384,10 @@ impl Step for CodegenGcc {
379384 }
380385
381386 fn metadata(&self) -> Option<StepMetadata> {
382 Some(StepMetadata::clippy("rustc_codegen_gcc", self.target).built_by(self.build_compiler))
387 Some(
388 StepMetadata::clippy("rustc_codegen_gcc", self.target)
389 .built_by(self.build_compiler.build_compiler()),
390 )
383391 }
384392}
385393
......@@ -396,7 +404,7 @@ macro_rules! lint_any {
396404
397405 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
398406 pub struct $name {
399 build_compiler: Compiler,
407 build_compiler: CompilerForCheck,
400408 target: TargetSelection,
401409 config: LintConfig,
402410 }
......@@ -419,9 +427,9 @@ macro_rules! lint_any {
419427 }
420428
421429 fn run(self, builder: &Builder<'_>) -> Self::Output {
422 let build_compiler = self.build_compiler;
430 let build_compiler = self.build_compiler.build_compiler();
423431 let target = self.target;
424 let cargo = prepare_tool_cargo(
432 let mut cargo = prepare_tool_cargo(
425433 builder,
426434 build_compiler,
427435 $mode,
......@@ -431,6 +439,7 @@ macro_rules! lint_any {
431439 SourceType::InTree,
432440 &[],
433441 );
442 self.build_compiler.configure_cargo(&mut cargo);
434443
435444 let _guard = builder.msg(
436445 Kind::Clippy,
......@@ -456,7 +465,7 @@ macro_rules! lint_any {
456465 }
457466
458467 fn metadata(&self) -> Option<StepMetadata> {
459 Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler))
468 Some(StepMetadata::clippy($readable_name, self.target).built_by(self.build_compiler.build_compiler()))
460469 }
461470 }
462471 )+
src/bootstrap/src/core/build_steps/compile.rs+78-34
......@@ -933,6 +933,15 @@ fn cp_rustc_component_to_ci_sysroot(builder: &Builder<'_>, sysroot: &Path, conte
933933 }
934934}
935935
936/// Represents information about a built rustc.
937#[derive(Clone, Debug)]
938pub struct BuiltRustc {
939 /// The compiler that actually built this *rustc*.
940 /// This can be different from the *build_compiler* passed to the `Rustc` step because of
941 /// uplifting.
942 pub build_compiler: Compiler,
943}
944
936945/// Build rustc using the passed `build_compiler`.
937946///
938947/// - Makes sure that `build_compiler` has a standard library prepared for its host target,
......@@ -960,7 +969,7 @@ impl Rustc {
960969}
961970
962971impl Step for Rustc {
963 type Output = ();
972 type Output = BuiltRustc;
964973
965974 const IS_HOST: bool = true;
966975 const DEFAULT: bool = false;
......@@ -1000,7 +1009,7 @@ impl Step for Rustc {
10001009 /// This will build the compiler for a particular stage of the build using
10011010 /// the `build_compiler` targeting the `target` architecture. The artifacts
10021011 /// created will also be linked into the sysroot directory.
1003 fn run(self, builder: &Builder<'_>) {
1012 fn run(self, builder: &Builder<'_>) -> Self::Output {
10041013 let build_compiler = self.build_compiler;
10051014 let target = self.target;
10061015
......@@ -1016,7 +1025,7 @@ impl Step for Rustc {
10161025 &sysroot,
10171026 builder.config.ci_rustc_dev_contents(),
10181027 );
1019 return;
1028 return BuiltRustc { build_compiler };
10201029 }
10211030
10221031 // Build a standard library for `target` using the `build_compiler`.
......@@ -1028,9 +1037,9 @@ impl Step for Rustc {
10281037
10291038 builder.info("WARNING: Using a potentially old librustc. This may not behave well.");
10301039 builder.info("WARNING: Use `--keep-stage-std` if you want to rebuild the compiler when it changes");
1031 builder.ensure(RustcLink::from_rustc(self, build_compiler));
1040 builder.ensure(RustcLink::from_rustc(self));
10321041
1033 return;
1042 return BuiltRustc { build_compiler };
10341043 }
10351044
10361045 // The stage of the compiler that we're building
......@@ -1042,21 +1051,35 @@ impl Step for Rustc {
10421051 && !builder.config.full_bootstrap
10431052 && (target == builder.host_target || builder.hosts.contains(&target))
10441053 {
1045 // If we're cross-compiling, the earliest rustc that we could have is stage 2.
1046 // If we're not cross-compiling, then we should have rustc stage 1.
1047 let stage_to_uplift = if target == builder.host_target { 1 } else { 2 };
1048 let rustc_to_uplift = builder.compiler(stage_to_uplift, target);
1049 let msg = if rustc_to_uplift.host == target {
1050 format!("Uplifting rustc (stage{} -> stage{stage})", rustc_to_uplift.stage,)
1054 // Here we need to determine the **build compiler** that built the stage that we will
1055 // be uplifting. We cannot uplift stage 1, as it has a different ABI than stage 2+,
1056 // so we always uplift the stage2 compiler (compiled with stage 1).
1057 let uplift_build_compiler = builder.compiler(1, build_compiler.host);
1058 let msg = if uplift_build_compiler.host == target {
1059 format!("Uplifting rustc (stage2 -> stage{stage})")
10511060 } else {
10521061 format!(
1053 "Uplifting rustc (stage{}:{} -> stage{stage}:{target})",
1054 rustc_to_uplift.stage, rustc_to_uplift.host,
1062 "Uplifting rustc (stage2:{} -> stage{stage}:{target})",
1063 uplift_build_compiler.host
10551064 )
10561065 };
10571066 builder.info(&msg);
1058 builder.ensure(RustcLink::from_rustc(self, rustc_to_uplift));
1059 return;
1067
1068 // Here the compiler that built the rlibs (`uplift_build_compiler`) can be different
1069 // from the compiler whose sysroot should be modified in this step. So we need to copy
1070 // the (previously built) rlibs into the correct sysroot.
1071 builder.ensure(RustcLink::from_build_compiler_and_sysroot(
1072 // This is the compiler that actually built the rustc rlibs
1073 uplift_build_compiler,
1074 // We copy the rlibs into the sysroot of `build_compiler`
1075 build_compiler,
1076 target,
1077 self.crates,
1078 ));
1079
1080 // Here we have performed an uplift, so we return the actual build compiler that "built"
1081 // this rustc.
1082 return BuiltRustc { build_compiler: uplift_build_compiler };
10601083 }
10611084
10621085 // Build a standard library for the current host target using the `build_compiler`.
......@@ -1129,10 +1152,8 @@ impl Step for Rustc {
11291152 strip_debug(builder, target, &target_root_dir.join("rustc-main"));
11301153 }
11311154
1132 builder.ensure(RustcLink::from_rustc(
1133 self,
1134 builder.compiler(build_compiler.stage, builder.config.host_target),
1135 ));
1155 builder.ensure(RustcLink::from_rustc(self));
1156 BuiltRustc { build_compiler }
11361157 }
11371158
11381159 fn metadata(&self) -> Option<StepMetadata> {
......@@ -1441,31 +1462,51 @@ fn rustc_llvm_env(builder: &Builder<'_>, cargo: &mut Cargo, target: TargetSelect
14411462 }
14421463}
14431464
1444/// `RustcLink` copies all of the rlibs from the rustc build into the previous stage's sysroot.
1465/// `RustcLink` copies compiler rlibs from a rustc build into a compiler sysroot.
1466/// It works with (potentially up to) three compilers:
1467/// - `build_compiler` is a compiler that built rustc rlibs
1468/// - `sysroot_compiler` is a compiler into whose sysroot we will copy the rlibs
1469/// - In most situations, `build_compiler` == `sysroot_compiler`
1470/// - `target_compiler` is the compiler whose rlibs were built. It is not represented explicitly
1471/// in this step, rather we just read the rlibs from a rustc build stamp of `build_compiler`.
1472///
14451473/// This is necessary for tools using `rustc_private`, where the previous compiler will build
14461474/// a tool against the next compiler.
14471475/// To build a tool against a compiler, the rlibs of that compiler that it links against
14481476/// must be in the sysroot of the compiler that's doing the compiling.
14491477#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14501478struct RustcLink {
1451 /// The compiler whose rlibs we are copying around.
1452 pub compiler: Compiler,
1453 /// This is the compiler into whose sysroot we want to copy the rlibs into.
1454 pub previous_stage_compiler: Compiler,
1455 pub target: TargetSelection,
1479 /// This compiler **built** some rustc, whose rlibs we will copy into a sysroot.
1480 build_compiler: Compiler,
1481 /// This is the compiler into whose sysroot we want to copy the built rlibs.
1482 /// In most cases, it will correspond to `build_compiler`.
1483 sysroot_compiler: Compiler,
1484 target: TargetSelection,
14561485 /// Not actually used; only present to make sure the cache invalidation is correct.
14571486 crates: Vec<String>,
14581487}
14591488
14601489impl RustcLink {
1461 fn from_rustc(rustc: Rustc, host_compiler: Compiler) -> Self {
1490 /// Copy rlibs from the build compiler that build this `rustc` into the sysroot of that
1491 /// build compiler.
1492 fn from_rustc(rustc: Rustc) -> Self {
14621493 Self {
1463 compiler: host_compiler,
1464 previous_stage_compiler: rustc.build_compiler,
1494 build_compiler: rustc.build_compiler,
1495 sysroot_compiler: rustc.build_compiler,
14651496 target: rustc.target,
14661497 crates: rustc.crates,
14671498 }
14681499 }
1500
1501 /// Copy rlibs **built** by `build_compiler` into the sysroot of `sysroot_compiler`.
1502 fn from_build_compiler_and_sysroot(
1503 build_compiler: Compiler,
1504 sysroot_compiler: Compiler,
1505 target: TargetSelection,
1506 crates: Vec<String>,
1507 ) -> Self {
1508 Self { build_compiler, sysroot_compiler, target, crates }
1509 }
14691510}
14701511
14711512impl Step for RustcLink {
......@@ -1477,14 +1518,14 @@ impl Step for RustcLink {
14771518
14781519 /// Same as `std_link`, only for librustc
14791520 fn run(self, builder: &Builder<'_>) {
1480 let compiler = self.compiler;
1481 let previous_stage_compiler = self.previous_stage_compiler;
1521 let build_compiler = self.build_compiler;
1522 let sysroot_compiler = self.sysroot_compiler;
14821523 let target = self.target;
14831524 add_to_sysroot(
14841525 builder,
1485 &builder.sysroot_target_libdir(previous_stage_compiler, target),
1486 &builder.sysroot_target_libdir(previous_stage_compiler, compiler.host),
1487 &build_stamp::librustc_stamp(builder, compiler, target),
1526 &builder.sysroot_target_libdir(sysroot_compiler, target),
1527 &builder.sysroot_target_libdir(sysroot_compiler, sysroot_compiler.host),
1528 &build_stamp::librustc_stamp(builder, build_compiler, target),
14881529 );
14891530 }
14901531}
......@@ -2099,7 +2140,10 @@ impl Step for Assemble {
20992140 "target_compiler.host" = ?target_compiler.host,
21002141 "building compiler libraries to link to"
21012142 );
2102 builder.ensure(Rustc::new(build_compiler, target_compiler.host));
2143
2144 // It is possible that an uplift has happened, so we override build_compiler here.
2145 let BuiltRustc { build_compiler } =
2146 builder.ensure(Rustc::new(build_compiler, target_compiler.host));
21032147
21042148 let stage = target_compiler.stage;
21052149 let host = target_compiler.host;
src/bootstrap/src/core/builder/cargo.rs+30
......@@ -101,6 +101,7 @@ pub struct Cargo {
101101impl Cargo {
102102 /// Calls [`Builder::cargo`] and [`Cargo::configure_linker`] to prepare an invocation of `cargo`
103103 /// to be run.
104 #[track_caller]
104105 pub fn new(
105106 builder: &Builder<'_>,
106107 compiler: Compiler,
......@@ -139,6 +140,7 @@ impl Cargo {
139140
140141 /// Same as [`Cargo::new`] except this one doesn't configure the linker with
141142 /// [`Cargo::configure_linker`].
143 #[track_caller]
142144 pub fn new_for_mir_opt_tests(
143145 builder: &Builder<'_>,
144146 compiler: Compiler,
......@@ -186,6 +188,32 @@ impl Cargo {
186188 self
187189 }
188190
191 /// Append a value to an env var of the cargo command instance.
192 /// If the variable was unset previously, this is equivalent to [`Cargo::env`].
193 /// If the variable was already set, this will append `delimiter` and then `value` to it.
194 ///
195 /// Note that this only considers the existence of the env. var. configured on this `Cargo`
196 /// instance. It does not look at the environment of this process.
197 pub fn append_to_env(
198 &mut self,
199 key: impl AsRef<OsStr>,
200 value: impl AsRef<OsStr>,
201 delimiter: impl AsRef<OsStr>,
202 ) -> &mut Cargo {
203 assert_ne!(key.as_ref(), "RUSTFLAGS");
204 assert_ne!(key.as_ref(), "RUSTDOCFLAGS");
205
206 let key = key.as_ref();
207 if let Some((_, Some(previous_value))) = self.command.get_envs().find(|(k, _)| *k == key) {
208 let mut combined: OsString = previous_value.to_os_string();
209 combined.push(delimiter.as_ref());
210 combined.push(value.as_ref());
211 self.env(key, combined)
212 } else {
213 self.env(key, value)
214 }
215 }
216
189217 pub fn add_rustc_lib_path(&mut self, builder: &Builder<'_>) {
190218 builder.add_rustc_lib_path(self.compiler, &mut self.command);
191219 }
......@@ -396,6 +424,7 @@ impl From<Cargo> for BootstrapCommand {
396424
397425impl Builder<'_> {
398426 /// Like [`Builder::cargo`], but only passes flags that are valid for all commands.
427 #[track_caller]
399428 pub fn bare_cargo(
400429 &self,
401430 compiler: Compiler,
......@@ -480,6 +509,7 @@ impl Builder<'_> {
480509 /// scoped by `mode`'s output directory, it will pass the `--target` flag for the specified
481510 /// `target`, and will be executing the Cargo command `cmd`. `cmd` can be `miri-cmd` for
482511 /// commands to be run with Miri.
512 #[track_caller]
483513 fn cargo(
484514 &self,
485515 compiler: Compiler,
src/bootstrap/src/core/builder/tests.rs+1-1
......@@ -1569,7 +1569,7 @@ mod snapshot {
15691569 [build] llvm <host>
15701570 [build] rustc 0 <host> -> rustc 1 <host>
15711571 [build] rustc 1 <host> -> std 1 <host>
1572 [build] rustc 1 <host> -> std 1 <target1>
1572 [check] rustc 1 <host> -> std 1 <target1>
15731573 [check] rustc 1 <host> -> rustc 2 <target1> (73 crates)
15741574 [check] rustc 1 <host> -> rustc 2 <target1>
15751575 [check] rustc 1 <host> -> Rustdoc 2 <target1>
src/build_helper/src/util.rs+11
......@@ -3,6 +3,8 @@ use std::io::{BufRead, BufReader};
33use std::path::Path;
44use std::process::Command;
55
6use crate::ci::CiEnv;
7
68/// Invokes `build_helper::util::detail_exit` with `cfg!(test)`
79///
810/// This is a macro instead of a function so that it uses `cfg(test)` in the *calling* crate, not in build helper.
......@@ -20,6 +22,15 @@ pub fn detail_exit(code: i32, is_test: bool) -> ! {
2022 if is_test {
2123 panic!("status code: {code}");
2224 } else {
25 // If we're in CI, print the current bootstrap invocation command, to make it easier to
26 // figure out what exactly has failed.
27 if CiEnv::is_ci() {
28 // Skip the first argument, as it will be some absolute path to the bootstrap binary.
29 let bootstrap_args =
30 std::env::args().skip(1).map(|a| a.to_string()).collect::<Vec<_>>().join(" ");
31 eprintln!("Bootstrap failed while executing `{bootstrap_args}`");
32 }
33
2334 // otherwise, exit with provided status code
2435 std::process::exit(code);
2536 }
src/ci/docker/host-x86_64/tidy/eslint.version+1-1
......@@ -1 +1 @@
18.6.0
\ No newline at end of file
18.57.1
src/doc/rustc-dev-guide/src/sanitizers.md+1-1
......@@ -45,7 +45,7 @@ implementation:
4545 [marked][sanitizer-attribute] with appropriate LLVM attribute:
4646 `SanitizeAddress`, `SanitizeHWAddress`, `SanitizeMemory`, or
4747 `SanitizeThread`. By default all functions are instrumented, but this
48 behaviour can be changed with `#[no_sanitize(...)]`.
48 behaviour can be changed with `#[sanitize(xyz = "on|off")]`.
4949
5050* The decision whether to perform instrumentation or not is possible only at a
5151 function granularity. In the cases were those decision differ between
src/doc/unstable-book/src/language-features/no-sanitize.md deleted-29
......@@ -1,29 +0,0 @@
1# `no_sanitize`
2
3The tracking issue for this feature is: [#39699]
4
5[#39699]: https://github.com/rust-lang/rust/issues/39699
6
7------------------------
8
9The `no_sanitize` attribute can be used to selectively disable sanitizer
10instrumentation in an annotated function. This might be useful to: avoid
11instrumentation overhead in a performance critical function, or avoid
12instrumenting code that contains constructs unsupported by given sanitizer.
13
14The precise effect of this annotation depends on particular sanitizer in use.
15For example, with `no_sanitize(thread)`, the thread sanitizer will no longer
16instrument non-atomic store / load operations, but it will instrument atomic
17operations to avoid reporting false positives and provide meaning full stack
18traces.
19
20## Examples
21
22``` rust
23#![feature(no_sanitize)]
24
25#[no_sanitize(address)]
26fn foo() {
27 // ...
28}
29```
src/doc/unstable-book/src/language-features/sanitize.md created+73
......@@ -0,0 +1,73 @@
1# `sanitize`
2
3The tracking issue for this feature is: [#39699]
4
5[#39699]: https://github.com/rust-lang/rust/issues/39699
6
7------------------------
8
9The `sanitize` attribute can be used to selectively disable or enable sanitizer
10instrumentation in an annotated function. This might be useful to: avoid
11instrumentation overhead in a performance critical function, or avoid
12instrumenting code that contains constructs unsupported by given sanitizer.
13
14The precise effect of this annotation depends on particular sanitizer in use.
15For example, with `sanitize(thread = "off")`, the thread sanitizer will no
16longer instrument non-atomic store / load operations, but it will instrument
17atomic operations to avoid reporting false positives and provide meaning full
18stack traces.
19
20This attribute was previously named `no_sanitize`.
21
22## Examples
23
24``` rust
25#![feature(sanitize)]
26
27#[sanitize(address = "off")]
28fn foo() {
29 // ...
30}
31```
32
33It is also possible to disable sanitizers for entire modules and enable them
34for single items or functions.
35
36```rust
37#![feature(sanitize)]
38
39#[sanitize(address = "off")]
40mod foo {
41 fn unsanitized() {
42 // ...
43 }
44
45 #[sanitize(address = "on")]
46 fn sanitized() {
47 // ...
48 }
49}
50```
51
52It's also applicable to impl blocks.
53
54```rust
55#![feature(sanitize)]
56
57trait MyTrait {
58 fn foo(&self);
59 fn bar(&self);
60}
61
62#[sanitize(address = "off")]
63impl MyTrait for () {
64 fn foo(&self) {
65 // ...
66 }
67
68 #[sanitize(address = "on")]
69 fn bar(&self) {
70 // ...
71 }
72}
73```
src/etc/htmldocck.py+6
......@@ -15,6 +15,7 @@ import os.path
1515import re
1616import shlex
1717from collections import namedtuple
18from pathlib import Path
1819
1920try:
2021 from html.parser import HTMLParser
......@@ -242,6 +243,11 @@ class CachedFiles(object):
242243 return self.last_path
243244
244245 def get_absolute_path(self, path):
246 if "*" in path:
247 paths = list(Path(self.root).glob(path))
248 if len(paths) != 1:
249 raise FailedCheck("glob path does not resolve to one file")
250 path = str(paths[0])
245251 return os.path.join(self.root, path)
246252
247253 def get_file(self, path):
src/librustdoc/Cargo.toml+1
......@@ -21,6 +21,7 @@ rustdoc-json-types = { path = "../rustdoc-json-types" }
2121serde = { version = "1.0", features = ["derive"] }
2222serde_json = "1.0"
2323smallvec = "1.8.1"
24stringdex = { version = "0.0.1-alpha4" }
2425tempfile = "3"
2526threadpool = "1.8.1"
2627tracing = "0.1"
src/librustdoc/build.rs+1
......@@ -10,6 +10,7 @@ fn main() {
1010 "static/css/normalize.css",
1111 "static/js/main.js",
1212 "static/js/search.js",
13 "static/js/stringdex.js",
1314 "static/js/settings.js",
1415 "static/js/src-script.js",
1516 "static/js/storage.js",
src/librustdoc/formats/cache.rs+2-4
......@@ -1,6 +1,5 @@
11use std::mem;
22
3use rustc_ast::join_path_syms;
43use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap, FxIndexSet};
54use rustc_hir::StabilityLevel;
65use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, DefIdSet};
......@@ -574,7 +573,6 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
574573 clean::ItemKind::ImportItem(import) => import.source.did.unwrap_or(item_def_id),
575574 _ => item_def_id,
576575 };
577 let path = join_path_syms(parent_path);
578576 let impl_id = if let Some(ParentStackItem::Impl { item_id, .. }) = cache.parent_stack.last() {
579577 item_id.as_def_id()
580578 } else {
......@@ -593,11 +591,11 @@ fn add_item_to_search_index(tcx: TyCtxt<'_>, cache: &mut Cache, item: &clean::It
593591 ty: item.type_(),
594592 defid: Some(defid),
595593 name,
596 path,
594 module_path: parent_path.to_vec(),
597595 desc,
598596 parent: parent_did,
599597 parent_idx: None,
600 exact_path: None,
598 exact_module_path: None,
601599 impl_id,
602600 search_type,
603601 aliases,
src/librustdoc/formats/item_type.rs+51-1
......@@ -4,7 +4,7 @@ use std::fmt;
44
55use rustc_hir::def::{CtorOf, DefKind, MacroKinds};
66use rustc_span::hygiene::MacroKind;
7use serde::{Serialize, Serializer};
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
88
99use crate::clean;
1010
......@@ -68,6 +68,52 @@ impl Serialize for ItemType {
6868 }
6969}
7070
71impl<'de> Deserialize<'de> for ItemType {
72 fn deserialize<D>(deserializer: D) -> Result<ItemType, D::Error>
73 where
74 D: Deserializer<'de>,
75 {
76 struct ItemTypeVisitor;
77 impl<'de> de::Visitor<'de> for ItemTypeVisitor {
78 type Value = ItemType;
79 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 write!(formatter, "an integer between 0 and 25")
81 }
82 fn visit_u64<E: de::Error>(self, v: u64) -> Result<ItemType, E> {
83 Ok(match v {
84 0 => ItemType::Keyword,
85 1 => ItemType::Primitive,
86 2 => ItemType::Module,
87 3 => ItemType::ExternCrate,
88 4 => ItemType::Import,
89 5 => ItemType::Struct,
90 6 => ItemType::Enum,
91 7 => ItemType::Function,
92 8 => ItemType::TypeAlias,
93 9 => ItemType::Static,
94 10 => ItemType::Trait,
95 11 => ItemType::Impl,
96 12 => ItemType::TyMethod,
97 13 => ItemType::Method,
98 14 => ItemType::StructField,
99 15 => ItemType::Variant,
100 16 => ItemType::Macro,
101 17 => ItemType::AssocType,
102 18 => ItemType::Constant,
103 19 => ItemType::AssocConst,
104 20 => ItemType::Union,
105 21 => ItemType::ForeignType,
106 23 => ItemType::ProcAttribute,
107 24 => ItemType::ProcDerive,
108 25 => ItemType::TraitAlias,
109 _ => return Err(E::missing_field("unknown number")),
110 })
111 }
112 }
113 deserializer.deserialize_any(ItemTypeVisitor)
114 }
115}
116
71117impl<'a> From<&'a clean::Item> for ItemType {
72118 fn from(item: &'a clean::Item) -> ItemType {
73119 let kind = match &item.kind {
......@@ -198,6 +244,10 @@ impl ItemType {
198244 pub(crate) fn is_adt(&self) -> bool {
199245 matches!(self, ItemType::Struct | ItemType::Union | ItemType::Enum)
200246 }
247 /// Keep this the same as isFnLikeTy in search.js
248 pub(crate) fn is_fn_like(&self) -> bool {
249 matches!(self, ItemType::Function | ItemType::Method | ItemType::TyMethod)
250 }
201251}
202252
203253impl fmt::Display for ItemType {
src/librustdoc/html/layout.rs+1
......@@ -27,6 +27,7 @@ pub(crate) struct Layout {
2727
2828pub(crate) struct Page<'a> {
2929 pub(crate) title: &'a str,
30 pub(crate) short_title: &'a str,
3031 pub(crate) css_class: &'a str,
3132 pub(crate) root_path: &'a str,
3233 pub(crate) static_root_path: Option<&'a str>,
src/librustdoc/html/render/context.rs+14
......@@ -204,6 +204,18 @@ impl<'tcx> Context<'tcx> {
204204 if !is_module {
205205 title.push_str(it.name.unwrap().as_str());
206206 }
207 let short_title;
208 let short_title = if is_module {
209 let module_name = self.current.last().unwrap();
210 short_title = if it.is_crate() {
211 format!("Crate {module_name}")
212 } else {
213 format!("Module {module_name}")
214 };
215 &short_title[..]
216 } else {
217 it.name.as_ref().unwrap().as_str()
218 };
207219 if !it.is_primitive() && !it.is_keyword() {
208220 if !is_module {
209221 title.push_str(" in ");
......@@ -240,6 +252,7 @@ impl<'tcx> Context<'tcx> {
240252 root_path: &self.root_path(),
241253 static_root_path: self.shared.static_root_path.as_deref(),
242254 title: &title,
255 short_title,
243256 description: &desc,
244257 resource_suffix: &self.shared.resource_suffix,
245258 rust_logo: has_doc_flag(self.tcx(), LOCAL_CRATE.as_def_id(), sym::rust_logo),
......@@ -617,6 +630,7 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
617630 let shared = &self.shared;
618631 let mut page = layout::Page {
619632 title: "List of all items in this crate",
633 short_title: "All",
620634 css_class: "mod sys",
621635 root_path: "../",
622636 static_root_path: shared.static_root_path.as_deref(),
src/librustdoc/html/render/mod.rs+144-21
......@@ -130,11 +130,11 @@ pub(crate) struct IndexItem {
130130 pub(crate) ty: ItemType,
131131 pub(crate) defid: Option<DefId>,
132132 pub(crate) name: Symbol,
133 pub(crate) path: String,
133 pub(crate) module_path: Vec<Symbol>,
134134 pub(crate) desc: String,
135135 pub(crate) parent: Option<DefId>,
136 pub(crate) parent_idx: Option<isize>,
137 pub(crate) exact_path: Option<String>,
136 pub(crate) parent_idx: Option<usize>,
137 pub(crate) exact_module_path: Option<Vec<Symbol>>,
138138 pub(crate) impl_id: Option<DefId>,
139139 pub(crate) search_type: Option<IndexItemFunctionType>,
140140 pub(crate) aliases: Box<[Symbol]>,
......@@ -150,6 +150,19 @@ struct RenderType {
150150}
151151
152152impl RenderType {
153 fn size(&self) -> usize {
154 let mut size = 1;
155 if let Some(generics) = &self.generics {
156 size += generics.iter().map(RenderType::size).sum::<usize>();
157 }
158 if let Some(bindings) = &self.bindings {
159 for (_, constraints) in bindings.iter() {
160 size += 1;
161 size += constraints.iter().map(RenderType::size).sum::<usize>();
162 }
163 }
164 size
165 }
153166 // Types are rendered as lists of lists, because that's pretty compact.
154167 // The contents of the lists are always integers in self-terminating hex
155168 // form, handled by `RenderTypeId::write_to_string`, so no commas are
......@@ -191,6 +204,62 @@ impl RenderType {
191204 write_optional_id(self.id, string);
192205 }
193206 }
207 fn read_from_bytes(string: &[u8]) -> (RenderType, usize) {
208 let mut i = 0;
209 if string[i] == b'{' {
210 i += 1;
211 let (id, offset) = RenderTypeId::read_from_bytes(&string[i..]);
212 i += offset;
213 let generics = if string[i] == b'{' {
214 i += 1;
215 let mut generics = Vec::new();
216 while string[i] != b'}' {
217 let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
218 i += offset;
219 generics.push(ty);
220 }
221 assert!(string[i] == b'}');
222 i += 1;
223 Some(generics)
224 } else {
225 None
226 };
227 let bindings = if string[i] == b'{' {
228 i += 1;
229 let mut bindings = Vec::new();
230 while string[i] == b'{' {
231 i += 1;
232 let (binding, boffset) = RenderTypeId::read_from_bytes(&string[i..]);
233 i += boffset;
234 let mut bconstraints = Vec::new();
235 assert!(string[i] == b'{');
236 i += 1;
237 while string[i] != b'}' {
238 let (constraint, coffset) = RenderType::read_from_bytes(&string[i..]);
239 i += coffset;
240 bconstraints.push(constraint);
241 }
242 assert!(string[i] == b'}');
243 i += 1;
244 bindings.push((binding.unwrap(), bconstraints));
245 assert!(string[i] == b'}');
246 i += 1;
247 }
248 assert!(string[i] == b'}');
249 i += 1;
250 Some(bindings)
251 } else {
252 None
253 };
254 assert!(string[i] == b'}');
255 i += 1;
256 (RenderType { id, generics, bindings }, i)
257 } else {
258 let (id, offset) = RenderTypeId::read_from_bytes(string);
259 i += offset;
260 (RenderType { id, generics: None, bindings: None }, i)
261 }
262 }
194263}
195264
196265#[derive(Clone, Copy, Debug, Eq, PartialEq)]
......@@ -212,7 +281,20 @@ impl RenderTypeId {
212281 RenderTypeId::Index(idx) => (*idx).try_into().unwrap(),
213282 _ => panic!("must convert render types to indexes before serializing"),
214283 };
215 search_index::encode::write_vlqhex_to_string(id, string);
284 search_index::encode::write_signed_vlqhex_to_string(id, string);
285 }
286 fn read_from_bytes(string: &[u8]) -> (Option<RenderTypeId>, usize) {
287 let Some((value, offset)) = search_index::encode::read_signed_vlqhex_from_string(string)
288 else {
289 return (None, 0);
290 };
291 let value = isize::try_from(value).unwrap();
292 let ty = match value {
293 ..0 => Some(RenderTypeId::Index(value)),
294 0 => None,
295 1.. => Some(RenderTypeId::Index(value - 1)),
296 };
297 (ty, offset)
216298 }
217299}
218300
......@@ -226,12 +308,64 @@ pub(crate) struct IndexItemFunctionType {
226308}
227309
228310impl IndexItemFunctionType {
229 fn write_to_string<'a>(
230 &'a self,
231 string: &mut String,
232 backref_queue: &mut VecDeque<&'a IndexItemFunctionType>,
233 ) {
234 assert!(backref_queue.len() <= 16);
311 fn size(&self) -> usize {
312 self.inputs.iter().map(RenderType::size).sum::<usize>()
313 + self.output.iter().map(RenderType::size).sum::<usize>()
314 + self
315 .where_clause
316 .iter()
317 .map(|constraints| constraints.iter().map(RenderType::size).sum::<usize>())
318 .sum::<usize>()
319 }
320 fn read_from_string_without_param_names(string: &[u8]) -> (IndexItemFunctionType, usize) {
321 let mut i = 0;
322 if string[i] == b'`' {
323 return (
324 IndexItemFunctionType {
325 inputs: Vec::new(),
326 output: Vec::new(),
327 where_clause: Vec::new(),
328 param_names: Vec::new(),
329 },
330 1,
331 );
332 }
333 assert_eq!(b'{', string[i]);
334 i += 1;
335 fn read_args_from_string(string: &[u8]) -> (Vec<RenderType>, usize) {
336 let mut i = 0;
337 let mut params = Vec::new();
338 if string[i] == b'{' {
339 // multiple params
340 i += 1;
341 while string[i] != b'}' {
342 let (ty, offset) = RenderType::read_from_bytes(&string[i..]);
343 i += offset;
344 params.push(ty);
345 }
346 i += 1;
347 } else if string[i] != b'}' {
348 let (tyid, offset) = RenderTypeId::read_from_bytes(&string[i..]);
349 params.push(RenderType { id: tyid, generics: None, bindings: None });
350 i += offset;
351 }
352 (params, i)
353 }
354 let (inputs, offset) = read_args_from_string(&string[i..]);
355 i += offset;
356 let (output, offset) = read_args_from_string(&string[i..]);
357 i += offset;
358 let mut where_clause = Vec::new();
359 while string[i] != b'}' {
360 let (constraint, offset) = read_args_from_string(&string[i..]);
361 i += offset;
362 where_clause.push(constraint);
363 }
364 assert_eq!(b'}', string[i], "{} {}", String::from_utf8_lossy(&string), i);
365 i += 1;
366 (IndexItemFunctionType { inputs, output, where_clause, param_names: Vec::new() }, i)
367 }
368 fn write_to_string_without_param_names<'a>(&'a self, string: &mut String) {
235369 // If we couldn't figure out a type, just write 0,
236370 // which is encoded as `` ` `` (see RenderTypeId::write_to_string).
237371 let has_missing = self
......@@ -241,18 +375,7 @@ impl IndexItemFunctionType {
241375 .any(|i| i.id.is_none() && i.generics.is_none());
242376 if has_missing {
243377 string.push('`');
244 } else if let Some(idx) = backref_queue.iter().position(|other| *other == self) {
245 // The backref queue has 16 items, so backrefs use
246 // a single hexit, disjoint from the ones used for numbers.
247 string.push(
248 char::try_from('0' as u32 + u32::try_from(idx).unwrap())
249 .expect("last possible value is '?'"),
250 );
251378 } else {
252 backref_queue.push_front(self);
253 if backref_queue.len() > 16 {
254 backref_queue.pop_back();
255 }
256379 string.push('{');
257380 match &self.inputs[..] {
258381 [one] if one.generics.is_none() && one.bindings.is_none() => {
src/librustdoc/html/render/print_item.rs+1
......@@ -35,6 +35,7 @@ use crate::html::format::{
3535 visibility_print_with_space,
3636};
3737use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
38use crate::html::render::sidebar::filters;
3839use crate::html::render::{document_full, document_item_info};
3940use crate::html::url_parts_builder::UrlPartsBuilder;
4041
src/librustdoc/html/render/search_index.rs+1565-612
......@@ -1,72 +1,1169 @@
11pub(crate) mod encode;
22
3use std::collections::BTreeSet;
34use std::collections::hash_map::Entry;
4use std::collections::{BTreeMap, VecDeque};
5use std::path::Path;
56
6use encode::{bitmap_to_string, write_vlqhex_to_string};
77use rustc_ast::join_path_syms;
8use rustc_data_structures::fx::{FxHashMap, FxIndexMap};
8use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
99use rustc_middle::ty::TyCtxt;
1010use rustc_span::def_id::DefId;
1111use rustc_span::sym;
1212use rustc_span::symbol::{Symbol, kw};
13use serde::ser::{Serialize, SerializeSeq, SerializeStruct, Serializer};
13use serde::de::{self, Deserializer, Error as _};
14use serde::ser::{SerializeSeq, Serializer};
15use serde::{Deserialize, Serialize};
16use stringdex::internals as stringdex_internals;
1417use thin_vec::ThinVec;
1518use tracing::instrument;
1619
1720use crate::clean::types::{Function, Generics, ItemId, Type, WherePredicate};
1821use crate::clean::{self, utils};
22use crate::error::Error;
1923use crate::formats::cache::{Cache, OrphanImplItem};
2024use crate::formats::item_type::ItemType;
2125use crate::html::markdown::short_markdown_summary;
22use crate::html::render::ordered_json::OrderedJson;
2326use crate::html::render::{self, IndexItem, IndexItemFunctionType, RenderType, RenderTypeId};
2427
25/// The serialized search description sharded version
26///
27/// The `index` is a JSON-encoded list of names and other information.
28///
29/// The desc has newlined descriptions, split up by size into 128KiB shards.
30/// For example, `(4, "foo\nbar\nbaz\nquux")`.
31///
32/// There is no single, optimal size for these shards, because it depends on
33/// configuration values that we can't predict or control, such as the version
34/// of HTTP used (HTTP/1.1 would work better with larger files, while HTTP/2
35/// and 3 are more agnostic), transport compression (gzip, zstd, etc), whether
36/// the search query is going to produce a large number of results or a small
37/// number, the bandwidth delay product of the network...
38///
39/// Gzipping some standard library descriptions to guess what transport
40/// compression will do, the compressed file sizes can be as small as 4.9KiB
41/// or as large as 18KiB (ignoring the final 1.9KiB shard of leftovers).
42/// A "reasonable" range for files is for them to be bigger than 1KiB,
43/// since that's about the amount of data that can be transferred in a
44/// single TCP packet, and 64KiB, the maximum amount of data that
45/// TCP can transfer in a single round trip without extensions.
46///
47/// [1]: https://en.wikipedia.org/wiki/Maximum_transmission_unit#MTUs_for_common_media
48/// [2]: https://en.wikipedia.org/wiki/Sliding_window_protocol#Basic_concept
49/// [3]: https://learn.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features
28#[derive(Clone, Debug, Default, Deserialize, Serialize)]
5029pub(crate) struct SerializedSearchIndex {
51 pub(crate) index: OrderedJson,
52 pub(crate) desc: Vec<(usize, String)>,
30 // data from disk
31 names: Vec<String>,
32 path_data: Vec<Option<PathData>>,
33 entry_data: Vec<Option<EntryData>>,
34 descs: Vec<String>,
35 function_data: Vec<Option<FunctionData>>,
36 alias_pointers: Vec<Option<usize>>,
37 // inverted index for concrete types and generics
38 type_data: Vec<Option<TypeData>>,
39 /// inverted index of generics
40 ///
41 /// - The outermost list has one entry per alpha-normalized generic.
42 ///
43 /// - The second layer is sorted by number of types that appear in the
44 /// type signature. The search engine iterates over these in order from
45 /// smallest to largest. Functions with less stuff in their type
46 /// signature are more likely to be what the user wants, because we never
47 /// show functions that are *missing* parts of the query, so removing..
48 ///
49 /// - The final layer is the list of functions.
50 generic_inverted_index: Vec<Vec<Vec<u32>>>,
51 // generated in-memory backref cache
52 #[serde(skip)]
53 crate_paths_index: FxHashMap<(ItemType, Vec<Symbol>), usize>,
54}
55
56impl SerializedSearchIndex {
57 fn load(doc_root: &Path, resource_suffix: &str) -> Result<SerializedSearchIndex, Error> {
58 let mut names: Vec<String> = Vec::new();
59 let mut path_data: Vec<Option<PathData>> = Vec::new();
60 let mut entry_data: Vec<Option<EntryData>> = Vec::new();
61 let mut descs: Vec<String> = Vec::new();
62 let mut function_data: Vec<Option<FunctionData>> = Vec::new();
63 let mut type_data: Vec<Option<TypeData>> = Vec::new();
64 let mut alias_pointers: Vec<Option<usize>> = Vec::new();
65
66 let mut generic_inverted_index: Vec<Vec<Vec<u32>>> = Vec::new();
67
68 match perform_read_strings(resource_suffix, doc_root, "name", &mut names) {
69 Ok(()) => {
70 perform_read_serde(resource_suffix, doc_root, "path", &mut path_data)?;
71 perform_read_serde(resource_suffix, doc_root, "entry", &mut entry_data)?;
72 perform_read_strings(resource_suffix, doc_root, "desc", &mut descs)?;
73 perform_read_serde(resource_suffix, doc_root, "function", &mut function_data)?;
74 perform_read_serde(resource_suffix, doc_root, "type", &mut type_data)?;
75 perform_read_serde(resource_suffix, doc_root, "alias", &mut alias_pointers)?;
76 perform_read_postings(
77 resource_suffix,
78 doc_root,
79 "generic_inverted_index",
80 &mut generic_inverted_index,
81 )?;
82 }
83 Err(_) => {
84 names.clear();
85 }
86 }
87 fn perform_read_strings(
88 resource_suffix: &str,
89 doc_root: &Path,
90 column_name: &str,
91 column: &mut Vec<String>,
92 ) -> Result<(), Error> {
93 let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
94 let column_path = doc_root.join(format!("search.index/{column_name}/"));
95 stringdex_internals::read_data_from_disk_column(
96 root_path,
97 column_name.as_bytes(),
98 column_path.clone(),
99 &mut |_id, item| {
100 column.push(String::from_utf8(item.to_vec())?);
101 Ok(())
102 },
103 )
104 .map_err(
105 |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
106 file: column_path,
107 error: format!("failed to read column from disk: {error}"),
108 },
109 )
110 }
111 fn perform_read_serde(
112 resource_suffix: &str,
113 doc_root: &Path,
114 column_name: &str,
115 column: &mut Vec<Option<impl for<'de> Deserialize<'de> + 'static>>,
116 ) -> Result<(), Error> {
117 let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
118 let column_path = doc_root.join(format!("search.index/{column_name}/"));
119 stringdex_internals::read_data_from_disk_column(
120 root_path,
121 column_name.as_bytes(),
122 column_path.clone(),
123 &mut |_id, item| {
124 if item.is_empty() {
125 column.push(None);
126 } else {
127 column.push(Some(serde_json::from_slice(item)?));
128 }
129 Ok(())
130 },
131 )
132 .map_err(
133 |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
134 file: column_path,
135 error: format!("failed to read column from disk: {error}"),
136 },
137 )
138 }
139 fn perform_read_postings(
140 resource_suffix: &str,
141 doc_root: &Path,
142 column_name: &str,
143 column: &mut Vec<Vec<Vec<u32>>>,
144 ) -> Result<(), Error> {
145 let root_path = doc_root.join(format!("search.index/root{resource_suffix}.js"));
146 let column_path = doc_root.join(format!("search.index/{column_name}/"));
147 stringdex_internals::read_data_from_disk_column(
148 root_path,
149 column_name.as_bytes(),
150 column_path.clone(),
151 &mut |_id, buf| {
152 let mut postings = Vec::new();
153 encode::read_postings_from_string(&mut postings, buf);
154 column.push(postings);
155 Ok(())
156 },
157 )
158 .map_err(
159 |error: stringdex_internals::ReadDataError<Box<dyn std::error::Error>>| Error {
160 file: column_path,
161 error: format!("failed to read column from disk: {error}"),
162 },
163 )
164 }
165
166 assert_eq!(names.len(), path_data.len());
167 assert_eq!(path_data.len(), entry_data.len());
168 assert_eq!(entry_data.len(), descs.len());
169 assert_eq!(descs.len(), function_data.len());
170 assert_eq!(function_data.len(), type_data.len());
171 assert_eq!(type_data.len(), alias_pointers.len());
172
173 // generic_inverted_index is not the same length as other columns,
174 // because it's actually a completely different set of objects
175
176 let mut crate_paths_index: FxHashMap<(ItemType, Vec<Symbol>), usize> = FxHashMap::default();
177 for (i, (name, path_data)) in names.iter().zip(path_data.iter()).enumerate() {
178 if let Some(path_data) = path_data {
179 let full_path = if path_data.module_path.is_empty() {
180 vec![Symbol::intern(name)]
181 } else {
182 let mut full_path = path_data.module_path.to_vec();
183 full_path.push(Symbol::intern(name));
184 full_path
185 };
186 crate_paths_index.insert((path_data.ty, full_path), i);
187 }
188 }
189
190 Ok(SerializedSearchIndex {
191 names,
192 path_data,
193 entry_data,
194 descs,
195 function_data,
196 type_data,
197 alias_pointers,
198 generic_inverted_index,
199 crate_paths_index,
200 })
201 }
202 fn push(
203 &mut self,
204 name: String,
205 path_data: Option<PathData>,
206 entry_data: Option<EntryData>,
207 desc: String,
208 function_data: Option<FunctionData>,
209 type_data: Option<TypeData>,
210 alias_pointer: Option<usize>,
211 ) -> usize {
212 let index = self.names.len();
213 assert_eq!(self.names.len(), self.path_data.len());
214 if let Some(path_data) = &path_data
215 && let name = Symbol::intern(&name)
216 && let fqp = if path_data.module_path.is_empty() {
217 vec![name]
218 } else {
219 let mut v = path_data.module_path.clone();
220 v.push(name);
221 v
222 }
223 && let Some(&other_path) = self.crate_paths_index.get(&(path_data.ty, fqp))
224 && self.path_data.get(other_path).map_or(false, Option::is_some)
225 {
226 self.path_data.push(None);
227 } else {
228 self.path_data.push(path_data);
229 }
230 self.names.push(name);
231 assert_eq!(self.entry_data.len(), self.descs.len());
232 self.entry_data.push(entry_data);
233 assert_eq!(self.descs.len(), self.function_data.len());
234 self.descs.push(desc);
235 assert_eq!(self.function_data.len(), self.type_data.len());
236 self.function_data.push(function_data);
237 assert_eq!(self.type_data.len(), self.alias_pointers.len());
238 self.type_data.push(type_data);
239 self.alias_pointers.push(alias_pointer);
240 index
241 }
242 fn push_path(&mut self, name: String, path_data: PathData) -> usize {
243 self.push(name, Some(path_data), None, String::new(), None, None, None)
244 }
245 fn push_type(&mut self, name: String, path_data: PathData, type_data: TypeData) -> usize {
246 self.push(name, Some(path_data), None, String::new(), None, Some(type_data), None)
247 }
248 fn push_alias(&mut self, name: String, alias_pointer: usize) -> usize {
249 self.push(name, None, None, String::new(), None, None, Some(alias_pointer))
250 }
251
252 fn get_id_by_module_path(&mut self, path: &[Symbol]) -> usize {
253 let ty = if path.len() == 1 { ItemType::ExternCrate } else { ItemType::Module };
254 match self.crate_paths_index.entry((ty, path.to_vec())) {
255 Entry::Occupied(index) => *index.get(),
256 Entry::Vacant(slot) => {
257 slot.insert(self.path_data.len());
258 let (name, module_path) = path.split_last().unwrap();
259 self.push_path(
260 name.as_str().to_string(),
261 PathData { ty, module_path: module_path.to_vec(), exact_module_path: None },
262 )
263 }
264 }
265 }
266
267 pub(crate) fn union(mut self, other: &SerializedSearchIndex) -> SerializedSearchIndex {
268 let other_entryid_offset = self.names.len();
269 let mut map_other_pathid_to_self_pathid: Vec<usize> = Vec::new();
270 let mut skips = FxHashSet::default();
271 for (other_pathid, other_path_data) in other.path_data.iter().enumerate() {
272 if let Some(other_path_data) = other_path_data {
273 let mut fqp = other_path_data.module_path.clone();
274 let name = Symbol::intern(&other.names[other_pathid]);
275 fqp.push(name);
276 let self_pathid = other_entryid_offset + other_pathid;
277 let self_pathid = match self.crate_paths_index.entry((other_path_data.ty, fqp)) {
278 Entry::Vacant(slot) => {
279 slot.insert(self_pathid);
280 self_pathid
281 }
282 Entry::Occupied(existing_entryid) => {
283 skips.insert(other_pathid);
284 let self_pathid = *existing_entryid.get();
285 let new_type_data = match (
286 self.type_data[self_pathid].take(),
287 other.type_data[other_pathid].as_ref(),
288 ) {
289 (Some(self_type_data), None) => Some(self_type_data),
290 (None, Some(other_type_data)) => Some(TypeData {
291 search_unbox: other_type_data.search_unbox,
292 inverted_function_signature_index: other_type_data
293 .inverted_function_signature_index
294 .iter()
295 .cloned()
296 .map(|mut list: Vec<u32>| {
297 for fnid in &mut list {
298 assert!(
299 other.function_data
300 [usize::try_from(*fnid).unwrap()]
301 .is_some(),
302 );
303 // this is valid because we call `self.push()` once, exactly, for every entry,
304 // even if we're just pushing a tombstone
305 *fnid += u32::try_from(other_entryid_offset).unwrap();
306 }
307 list
308 })
309 .collect(),
310 }),
311 (Some(mut self_type_data), Some(other_type_data)) => {
312 for (size, other_list) in other_type_data
313 .inverted_function_signature_index
314 .iter()
315 .enumerate()
316 {
317 while self_type_data.inverted_function_signature_index.len()
318 <= size
319 {
320 self_type_data
321 .inverted_function_signature_index
322 .push(Vec::new());
323 }
324 self_type_data.inverted_function_signature_index[size].extend(
325 other_list.iter().copied().map(|fnid| {
326 assert!(
327 other.function_data[usize::try_from(fnid).unwrap()]
328 .is_some(),
329 );
330 // this is valid because we call `self.push()` once, exactly, for every entry,
331 // even if we're just pushing a tombstone
332 fnid + u32::try_from(other_entryid_offset).unwrap()
333 }),
334 )
335 }
336 Some(self_type_data)
337 }
338 (None, None) => None,
339 };
340 self.type_data[self_pathid] = new_type_data;
341 self_pathid
342 }
343 };
344 map_other_pathid_to_self_pathid.push(self_pathid);
345 } else {
346 // if this gets used, we want it to crash
347 // this should be impossible as a valid index, since some of the
348 // memory must be used for stuff other than the list
349 map_other_pathid_to_self_pathid.push(!0);
350 }
351 }
352 for other_entryid in 0..other.names.len() {
353 if skips.contains(&other_entryid) {
354 // we push tombstone entries to keep the IDs lined up
355 self.push(String::new(), None, None, String::new(), None, None, None);
356 } else {
357 self.push(
358 other.names[other_entryid].clone(),
359 other.path_data[other_entryid].clone(),
360 other.entry_data[other_entryid].as_ref().map(|other_entry_data| EntryData {
361 parent: other_entry_data
362 .parent
363 .map(|parent| map_other_pathid_to_self_pathid[parent])
364 .clone(),
365 module_path: other_entry_data
366 .module_path
367 .map(|path| map_other_pathid_to_self_pathid[path])
368 .clone(),
369 exact_module_path: other_entry_data
370 .exact_module_path
371 .map(|exact_path| map_other_pathid_to_self_pathid[exact_path])
372 .clone(),
373 krate: map_other_pathid_to_self_pathid[other_entry_data.krate],
374 ..other_entry_data.clone()
375 }),
376 other.descs[other_entryid].clone(),
377 other.function_data[other_entryid].as_ref().map(|function_data| FunctionData {
378 function_signature: {
379 let (mut func, _offset) =
380 IndexItemFunctionType::read_from_string_without_param_names(
381 function_data.function_signature.as_bytes(),
382 );
383 fn map_fn_sig_item(
384 map_other_pathid_to_self_pathid: &mut Vec<usize>,
385 ty: &mut RenderType,
386 ) {
387 match ty.id {
388 None => {}
389 Some(RenderTypeId::Index(generic)) if generic < 0 => {}
390 Some(RenderTypeId::Index(id)) => {
391 let id = usize::try_from(id).unwrap();
392 let id = map_other_pathid_to_self_pathid[id];
393 assert!(id != !0);
394 ty.id =
395 Some(RenderTypeId::Index(isize::try_from(id).unwrap()));
396 }
397 _ => unreachable!(),
398 }
399 if let Some(generics) = &mut ty.generics {
400 for generic in generics {
401 map_fn_sig_item(map_other_pathid_to_self_pathid, generic);
402 }
403 }
404 if let Some(bindings) = &mut ty.bindings {
405 for (param, constraints) in bindings {
406 *param = match *param {
407 param @ RenderTypeId::Index(generic) if generic < 0 => {
408 param
409 }
410 RenderTypeId::Index(id) => {
411 let id = usize::try_from(id).unwrap();
412 let id = map_other_pathid_to_self_pathid[id];
413 assert!(id != !0);
414 RenderTypeId::Index(isize::try_from(id).unwrap())
415 }
416 _ => unreachable!(),
417 };
418 for constraint in constraints {
419 map_fn_sig_item(
420 map_other_pathid_to_self_pathid,
421 constraint,
422 );
423 }
424 }
425 }
426 }
427 for input in &mut func.inputs {
428 map_fn_sig_item(&mut map_other_pathid_to_self_pathid, input);
429 }
430 for output in &mut func.output {
431 map_fn_sig_item(&mut map_other_pathid_to_self_pathid, output);
432 }
433 for clause in &mut func.where_clause {
434 for entry in clause {
435 map_fn_sig_item(&mut map_other_pathid_to_self_pathid, entry);
436 }
437 }
438 let mut result =
439 String::with_capacity(function_data.function_signature.len());
440 func.write_to_string_without_param_names(&mut result);
441 result
442 },
443 param_names: function_data.param_names.clone(),
444 }),
445 other.type_data[other_entryid].as_ref().map(|type_data| TypeData {
446 inverted_function_signature_index: type_data
447 .inverted_function_signature_index
448 .iter()
449 .cloned()
450 .map(|mut list| {
451 for fnid in &mut list {
452 assert!(
453 other.function_data[usize::try_from(*fnid).unwrap()]
454 .is_some(),
455 );
456 // this is valid because we call `self.push()` once, exactly, for every entry,
457 // even if we're just pushing a tombstone
458 *fnid += u32::try_from(other_entryid_offset).unwrap();
459 }
460 list
461 })
462 .collect(),
463 search_unbox: type_data.search_unbox,
464 }),
465 other.alias_pointers[other_entryid]
466 .map(|alias_pointer| alias_pointer + other_entryid_offset),
467 );
468 }
469 }
470 for (i, other_generic_inverted_index) in other.generic_inverted_index.iter().enumerate() {
471 for (size, other_list) in other_generic_inverted_index.iter().enumerate() {
472 let self_generic_inverted_index = match self.generic_inverted_index.get_mut(i) {
473 Some(self_generic_inverted_index) => self_generic_inverted_index,
474 None => {
475 self.generic_inverted_index.push(Vec::new());
476 self.generic_inverted_index.last_mut().unwrap()
477 }
478 };
479 while self_generic_inverted_index.len() <= size {
480 self_generic_inverted_index.push(Vec::new());
481 }
482 self_generic_inverted_index[size].extend(
483 other_list
484 .iter()
485 .copied()
486 .map(|fnid| fnid + u32::try_from(other_entryid_offset).unwrap()),
487 );
488 }
489 }
490 self
491 }
492
493 pub(crate) fn sort(self) -> SerializedSearchIndex {
494 let mut idlist: Vec<usize> = (0..self.names.len()).collect();
495 // nameless entries are tombstones, and will be removed after sorting
496 // sort shorter names first, so that we can present them in order out of search.js
497 idlist.sort_by_key(|&id| {
498 (
499 self.names[id].is_empty(),
500 self.names[id].len(),
501 &self.names[id],
502 self.entry_data[id].as_ref().map_or("", |entry| self.names[entry.krate].as_str()),
503 self.path_data[id].as_ref().map_or(&[][..], |entry| &entry.module_path[..]),
504 )
505 });
506 let map = FxHashMap::from_iter(
507 idlist.iter().enumerate().map(|(new_id, &old_id)| (old_id, new_id)),
508 );
509 let mut new = SerializedSearchIndex::default();
510 for &id in &idlist {
511 if self.names[id].is_empty() {
512 break;
513 }
514 new.push(
515 self.names[id].clone(),
516 self.path_data[id].clone(),
517 self.entry_data[id].as_ref().map(
518 |EntryData {
519 krate,
520 ty,
521 module_path,
522 exact_module_path,
523 parent,
524 deprecated,
525 associated_item_disambiguator,
526 }| EntryData {
527 krate: *map.get(krate).unwrap(),
528 ty: *ty,
529 module_path: module_path.and_then(|path_id| map.get(&path_id).copied()),
530 exact_module_path: exact_module_path
531 .and_then(|path_id| map.get(&path_id).copied()),
532 parent: parent.and_then(|path_id| map.get(&path_id).copied()),
533 deprecated: *deprecated,
534 associated_item_disambiguator: associated_item_disambiguator.clone(),
535 },
536 ),
537 self.descs[id].clone(),
538 self.function_data[id].as_ref().map(
539 |FunctionData { function_signature, param_names }| FunctionData {
540 function_signature: {
541 let (mut func, _offset) =
542 IndexItemFunctionType::read_from_string_without_param_names(
543 function_signature.as_bytes(),
544 );
545 fn map_fn_sig_item(map: &FxHashMap<usize, usize>, ty: &mut RenderType) {
546 match ty.id {
547 None => {}
548 Some(RenderTypeId::Index(generic)) if generic < 0 => {}
549 Some(RenderTypeId::Index(id)) => {
550 let id = usize::try_from(id).unwrap();
551 let id = *map.get(&id).unwrap();
552 assert!(id != !0);
553 ty.id =
554 Some(RenderTypeId::Index(isize::try_from(id).unwrap()));
555 }
556 _ => unreachable!(),
557 }
558 if let Some(generics) = &mut ty.generics {
559 for generic in generics {
560 map_fn_sig_item(map, generic);
561 }
562 }
563 if let Some(bindings) = &mut ty.bindings {
564 for (param, constraints) in bindings {
565 *param = match *param {
566 param @ RenderTypeId::Index(generic) if generic < 0 => {
567 param
568 }
569 RenderTypeId::Index(id) => {
570 let id = usize::try_from(id).unwrap();
571 let id = *map.get(&id).unwrap();
572 assert!(id != !0);
573 RenderTypeId::Index(isize::try_from(id).unwrap())
574 }
575 _ => unreachable!(),
576 };
577 for constraint in constraints {
578 map_fn_sig_item(map, constraint);
579 }
580 }
581 }
582 }
583 for input in &mut func.inputs {
584 map_fn_sig_item(&map, input);
585 }
586 for output in &mut func.output {
587 map_fn_sig_item(&map, output);
588 }
589 for clause in &mut func.where_clause {
590 for entry in clause {
591 map_fn_sig_item(&map, entry);
592 }
593 }
594 let mut result = String::with_capacity(function_signature.len());
595 func.write_to_string_without_param_names(&mut result);
596 result
597 },
598 param_names: param_names.clone(),
599 },
600 ),
601 self.type_data[id].as_ref().map(
602 |TypeData { search_unbox, inverted_function_signature_index }| {
603 let inverted_function_signature_index: Vec<Vec<u32>> =
604 inverted_function_signature_index
605 .iter()
606 .cloned()
607 .map(|mut list| {
608 for id in &mut list {
609 *id = u32::try_from(
610 *map.get(&usize::try_from(*id).unwrap()).unwrap(),
611 )
612 .unwrap();
613 }
614 list.sort();
615 list
616 })
617 .collect();
618 TypeData { search_unbox: *search_unbox, inverted_function_signature_index }
619 },
620 ),
621 self.alias_pointers[id].and_then(|alias| map.get(&alias).copied()),
622 );
623 }
624 new.generic_inverted_index = self
625 .generic_inverted_index
626 .into_iter()
627 .map(|mut postings| {
628 for list in postings.iter_mut() {
629 let mut new_list: Vec<u32> = list
630 .iter()
631 .copied()
632 .filter_map(|id| u32::try_from(*map.get(&usize::try_from(id).ok()?)?).ok())
633 .collect();
634 new_list.sort();
635 *list = new_list;
636 }
637 postings
638 })
639 .collect();
640 new
641 }
642
643 pub(crate) fn write_to(self, doc_root: &Path, resource_suffix: &str) -> Result<(), Error> {
644 let SerializedSearchIndex {
645 names,
646 path_data,
647 entry_data,
648 descs,
649 function_data,
650 type_data,
651 alias_pointers,
652 generic_inverted_index,
653 crate_paths_index: _,
654 } = self;
655 let mut serialized_root = Vec::new();
656 serialized_root.extend_from_slice(br#"rr_('{"normalizedName":{"I":""#);
657 let normalized_names = names
658 .iter()
659 .map(|name| {
660 if name.contains("_") {
661 name.replace("_", "").to_ascii_lowercase()
662 } else {
663 name.to_ascii_lowercase()
664 }
665 })
666 .collect::<Vec<String>>();
667 let names_search_tree = stringdex_internals::tree::encode_search_tree_ukkonen(
668 normalized_names.iter().map(|name| name.as_bytes()),
669 );
670 let dir_path = doc_root.join(format!("search.index/"));
671 let _ = std::fs::remove_dir_all(&dir_path); // if already missing, no problem
672 stringdex_internals::write_tree_to_disk(
673 &names_search_tree,
674 &dir_path,
675 &mut serialized_root,
676 )
677 .map_err(|error| Error {
678 file: dir_path,
679 error: format!("failed to write name tree to disk: {error}"),
680 })?;
681 std::mem::drop(names_search_tree);
682 serialized_root.extend_from_slice(br#"","#);
683 serialized_root.extend_from_slice(&perform_write_strings(
684 doc_root,
685 "normalizedName",
686 normalized_names.into_iter(),
687 )?);
688 serialized_root.extend_from_slice(br#"},"crateNames":{"#);
689 let mut crates: Vec<&[u8]> = entry_data
690 .iter()
691 .filter_map(|entry_data| Some(names[entry_data.as_ref()?.krate].as_bytes()))
692 .collect();
693 crates.sort();
694 crates.dedup();
695 serialized_root.extend_from_slice(&perform_write_strings(
696 doc_root,
697 "crateNames",
698 crates.into_iter(),
699 )?);
700 serialized_root.extend_from_slice(br#"},"name":{"#);
701 serialized_root.extend_from_slice(&perform_write_strings(doc_root, "name", names.iter())?);
702 serialized_root.extend_from_slice(br#"},"path":{"#);
703 serialized_root.extend_from_slice(&perform_write_serde(doc_root, "path", path_data)?);
704 serialized_root.extend_from_slice(br#"},"entry":{"#);
705 serialized_root.extend_from_slice(&perform_write_serde(doc_root, "entry", entry_data)?);
706 serialized_root.extend_from_slice(br#"},"desc":{"#);
707 serialized_root.extend_from_slice(&perform_write_strings(
708 doc_root,
709 "desc",
710 descs.into_iter(),
711 )?);
712 serialized_root.extend_from_slice(br#"},"function":{"#);
713 serialized_root.extend_from_slice(&perform_write_serde(
714 doc_root,
715 "function",
716 function_data,
717 )?);
718 serialized_root.extend_from_slice(br#"},"type":{"#);
719 serialized_root.extend_from_slice(&perform_write_serde(doc_root, "type", type_data)?);
720 serialized_root.extend_from_slice(br#"},"alias":{"#);
721 serialized_root.extend_from_slice(&perform_write_serde(doc_root, "alias", alias_pointers)?);
722 serialized_root.extend_from_slice(br#"},"generic_inverted_index":{"#);
723 serialized_root.extend_from_slice(&perform_write_postings(
724 doc_root,
725 "generic_inverted_index",
726 generic_inverted_index,
727 )?);
728 serialized_root.extend_from_slice(br#"}}')"#);
729 fn perform_write_strings(
730 doc_root: &Path,
731 dirname: &str,
732 mut column: impl Iterator<Item = impl AsRef<[u8]> + Clone> + ExactSizeIterator,
733 ) -> Result<Vec<u8>, Error> {
734 let dir_path = doc_root.join(format!("search.index/{dirname}"));
735 stringdex_internals::write_data_to_disk(&mut column, &dir_path).map_err(|error| Error {
736 file: dir_path,
737 error: format!("failed to write column to disk: {error}"),
738 })
739 }
740 fn perform_write_serde(
741 doc_root: &Path,
742 dirname: &str,
743 column: Vec<Option<impl Serialize>>,
744 ) -> Result<Vec<u8>, Error> {
745 perform_write_strings(
746 doc_root,
747 dirname,
748 column.into_iter().map(|value| {
749 if let Some(value) = value {
750 serde_json::to_vec(&value).unwrap()
751 } else {
752 Vec::new()
753 }
754 }),
755 )
756 }
757 fn perform_write_postings(
758 doc_root: &Path,
759 dirname: &str,
760 column: Vec<Vec<Vec<u32>>>,
761 ) -> Result<Vec<u8>, Error> {
762 perform_write_strings(
763 doc_root,
764 dirname,
765 column.into_iter().map(|postings| {
766 let mut buf = Vec::new();
767 encode::write_postings_to_string(&postings, &mut buf);
768 buf
769 }),
770 )
771 }
772 std::fs::write(
773 doc_root.join(format!("search.index/root{resource_suffix}.js")),
774 serialized_root,
775 )
776 .map_err(|error| Error {
777 file: doc_root.join(format!("search.index/root{resource_suffix}.js")),
778 error: format!("failed to write root to disk: {error}"),
779 })?;
780 Ok(())
781 }
782}
783
784#[derive(Clone, Debug)]
785struct EntryData {
786 krate: usize,
787 ty: ItemType,
788 module_path: Option<usize>,
789 exact_module_path: Option<usize>,
790 parent: Option<usize>,
791 deprecated: bool,
792 associated_item_disambiguator: Option<String>,
793}
794
795impl Serialize for EntryData {
796 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
797 where
798 S: Serializer,
799 {
800 let mut seq = serializer.serialize_seq(None)?;
801 seq.serialize_element(&self.krate)?;
802 seq.serialize_element(&self.ty)?;
803 seq.serialize_element(&self.module_path.map(|id| id + 1).unwrap_or(0))?;
804 seq.serialize_element(&self.exact_module_path.map(|id| id + 1).unwrap_or(0))?;
805 seq.serialize_element(&self.parent.map(|id| id + 1).unwrap_or(0))?;
806 seq.serialize_element(&if self.deprecated { 1 } else { 0 })?;
807 if let Some(disambig) = &self.associated_item_disambiguator {
808 seq.serialize_element(&disambig)?;
809 }
810 seq.end()
811 }
812}
813
814impl<'de> Deserialize<'de> for EntryData {
815 fn deserialize<D>(deserializer: D) -> Result<EntryData, D::Error>
816 where
817 D: Deserializer<'de>,
818 {
819 struct EntryDataVisitor;
820 impl<'de> de::Visitor<'de> for EntryDataVisitor {
821 type Value = EntryData;
822 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
823 write!(formatter, "path data")
824 }
825 fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<EntryData, A::Error> {
826 let krate: usize =
827 v.next_element()?.ok_or_else(|| A::Error::missing_field("krate"))?;
828 let ty: ItemType =
829 v.next_element()?.ok_or_else(|| A::Error::missing_field("ty"))?;
830 let module_path: SerializedOptional32 =
831 v.next_element()?.ok_or_else(|| A::Error::missing_field("module_path"))?;
832 let exact_module_path: SerializedOptional32 = v
833 .next_element()?
834 .ok_or_else(|| A::Error::missing_field("exact_module_path"))?;
835 let parent: SerializedOptional32 =
836 v.next_element()?.ok_or_else(|| A::Error::missing_field("parent"))?;
837 let deprecated: u32 = v.next_element()?.unwrap_or(0);
838 let associated_item_disambiguator: Option<String> = v.next_element()?;
839 Ok(EntryData {
840 krate,
841 ty,
842 module_path: Option::<i32>::from(module_path).map(|path| path as usize),
843 exact_module_path: Option::<i32>::from(exact_module_path)
844 .map(|path| path as usize),
845 parent: Option::<i32>::from(parent).map(|path| path as usize),
846 deprecated: deprecated != 0,
847 associated_item_disambiguator,
848 })
849 }
850 }
851 deserializer.deserialize_any(EntryDataVisitor)
852 }
853}
854
855#[derive(Clone, Debug)]
856struct PathData {
857 ty: ItemType,
858 module_path: Vec<Symbol>,
859 exact_module_path: Option<Vec<Symbol>>,
53860}
54861
55const DESC_INDEX_SHARD_LEN: usize = 128 * 1024;
862impl Serialize for PathData {
863 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
864 where
865 S: Serializer,
866 {
867 let mut seq = serializer.serialize_seq(None)?;
868 seq.serialize_element(&self.ty)?;
869 seq.serialize_element(&if self.module_path.is_empty() {
870 String::new()
871 } else {
872 join_path_syms(&self.module_path)
873 })?;
874 if let Some(ref path) = self.exact_module_path {
875 seq.serialize_element(&if path.is_empty() {
876 String::new()
877 } else {
878 join_path_syms(path)
879 })?;
880 }
881 seq.end()
882 }
883}
884
885impl<'de> Deserialize<'de> for PathData {
886 fn deserialize<D>(deserializer: D) -> Result<PathData, D::Error>
887 where
888 D: Deserializer<'de>,
889 {
890 struct PathDataVisitor;
891 impl<'de> de::Visitor<'de> for PathDataVisitor {
892 type Value = PathData;
893 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
894 write!(formatter, "path data")
895 }
896 fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<PathData, A::Error> {
897 let ty: ItemType =
898 v.next_element()?.ok_or_else(|| A::Error::missing_field("ty"))?;
899 let module_path: String =
900 v.next_element()?.ok_or_else(|| A::Error::missing_field("module_path"))?;
901 let exact_module_path: Option<String> =
902 v.next_element()?.and_then(SerializedOptionalString::into);
903 Ok(PathData {
904 ty,
905 module_path: if module_path.is_empty() {
906 vec![]
907 } else {
908 module_path.split("::").map(Symbol::intern).collect()
909 },
910 exact_module_path: exact_module_path.map(|path| {
911 if path.is_empty() {
912 vec![]
913 } else {
914 path.split("::").map(Symbol::intern).collect()
915 }
916 }),
917 })
918 }
919 }
920 deserializer.deserialize_any(PathDataVisitor)
921 }
922}
923
924#[derive(Clone, Debug)]
925struct TypeData {
926 /// If set to "true", the generics can be matched without having to
927 /// mention the type itself. The truth table, assuming `Unboxable`
928 /// has `search_unbox = true` and `Inner` has `search_unbox = false`
929 ///
930 /// | **query** | `Unboxable<Inner>` | `Inner` | `Inner<Unboxable>` |
931 /// |--------------------|--------------------|---------|--------------------|
932 /// | `Inner` | yes | yes | yes |
933 /// | `Unboxable` | yes | no | no |
934 /// | `Unboxable<Inner>` | yes | no | no |
935 /// | `Inner<Unboxable>` | no | no | yes |
936 search_unbox: bool,
937 /// List of functions that mention this type in their type signature.
938 ///
939 /// - The outermost list has one entry per alpha-normalized generic.
940 ///
941 /// - The second layer is sorted by number of types that appear in the
942 /// type signature. The search engine iterates over these in order from
943 /// smallest to largest. Functions with less stuff in their type
944 /// signature are more likely to be what the user wants, because we never
945 /// show functions that are *missing* parts of the query, so removing..
946 ///
947 /// - The final layer is the list of functions.
948 inverted_function_signature_index: Vec<Vec<u32>>,
949}
950
951impl Serialize for TypeData {
952 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
953 where
954 S: Serializer,
955 {
956 if self.search_unbox || !self.inverted_function_signature_index.is_empty() {
957 let mut seq = serializer.serialize_seq(None)?;
958 if !self.inverted_function_signature_index.is_empty() {
959 let mut buf = Vec::new();
960 encode::write_postings_to_string(&self.inverted_function_signature_index, &mut buf);
961 let mut serialized_result = Vec::new();
962 stringdex_internals::encode::write_base64_to_bytes(&buf, &mut serialized_result);
963 seq.serialize_element(&String::from_utf8(serialized_result).unwrap())?;
964 }
965 if self.search_unbox {
966 seq.serialize_element(&1)?;
967 }
968 seq.end()
969 } else {
970 None::<()>.serialize(serializer)
971 }
972 }
973}
974
975impl<'de> Deserialize<'de> for TypeData {
976 fn deserialize<D>(deserializer: D) -> Result<TypeData, D::Error>
977 where
978 D: Deserializer<'de>,
979 {
980 struct TypeDataVisitor;
981 impl<'de> de::Visitor<'de> for TypeDataVisitor {
982 type Value = TypeData;
983 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
984 write!(formatter, "type data")
985 }
986 fn visit_none<E>(self) -> Result<TypeData, E> {
987 Ok(TypeData { inverted_function_signature_index: vec![], search_unbox: false })
988 }
989 fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<TypeData, A::Error> {
990 let inverted_function_signature_index: String =
991 v.next_element()?.unwrap_or(String::new());
992 let search_unbox: u32 = v.next_element()?.unwrap_or(0);
993 let mut idx: Vec<u8> = Vec::new();
994 stringdex_internals::decode::read_base64_from_bytes(
995 inverted_function_signature_index.as_bytes(),
996 &mut idx,
997 )
998 .unwrap();
999 let mut inverted_function_signature_index = Vec::new();
1000 encode::read_postings_from_string(&mut inverted_function_signature_index, &idx);
1001 Ok(TypeData { inverted_function_signature_index, search_unbox: search_unbox == 1 })
1002 }
1003 }
1004 deserializer.deserialize_any(TypeDataVisitor)
1005 }
1006}
1007
1008enum SerializedOptionalString {
1009 None,
1010 Some(String),
1011}
1012
1013impl From<SerializedOptionalString> for Option<String> {
1014 fn from(me: SerializedOptionalString) -> Option<String> {
1015 match me {
1016 SerializedOptionalString::Some(string) => Some(string),
1017 SerializedOptionalString::None => None,
1018 }
1019 }
1020}
1021
1022impl Serialize for SerializedOptionalString {
1023 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1024 where
1025 S: Serializer,
1026 {
1027 match self {
1028 SerializedOptionalString::Some(string) => string.serialize(serializer),
1029 SerializedOptionalString::None => 0.serialize(serializer),
1030 }
1031 }
1032}
1033impl<'de> Deserialize<'de> for SerializedOptionalString {
1034 fn deserialize<D>(deserializer: D) -> Result<SerializedOptionalString, D::Error>
1035 where
1036 D: Deserializer<'de>,
1037 {
1038 struct SerializedOptionalStringVisitor;
1039 impl<'de> de::Visitor<'de> for SerializedOptionalStringVisitor {
1040 type Value = SerializedOptionalString;
1041 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1042 write!(formatter, "0 or string")
1043 }
1044 fn visit_u64<E: de::Error>(self, v: u64) -> Result<SerializedOptionalString, E> {
1045 if v != 0 {
1046 return Err(E::missing_field("not 0"));
1047 }
1048 Ok(SerializedOptionalString::None)
1049 }
1050 fn visit_string<E: de::Error>(self, v: String) -> Result<SerializedOptionalString, E> {
1051 Ok(SerializedOptionalString::Some(v))
1052 }
1053 fn visit_str<E: de::Error>(self, v: &str) -> Result<SerializedOptionalString, E> {
1054 Ok(SerializedOptionalString::Some(v.to_string()))
1055 }
1056 }
1057 deserializer.deserialize_any(SerializedOptionalStringVisitor)
1058 }
1059}
1060
1061enum SerializedOptional32 {
1062 None,
1063 Some(i32),
1064}
1065
1066impl From<SerializedOptional32> for Option<i32> {
1067 fn from(me: SerializedOptional32) -> Option<i32> {
1068 match me {
1069 SerializedOptional32::Some(number) => Some(number),
1070 SerializedOptional32::None => None,
1071 }
1072 }
1073}
1074
1075impl Serialize for SerializedOptional32 {
1076 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1077 where
1078 S: Serializer,
1079 {
1080 match self {
1081 &SerializedOptional32::Some(number) if number < 0 => number.serialize(serializer),
1082 &SerializedOptional32::Some(number) => (number + 1).serialize(serializer),
1083 &SerializedOptional32::None => 0.serialize(serializer),
1084 }
1085 }
1086}
1087impl<'de> Deserialize<'de> for SerializedOptional32 {
1088 fn deserialize<D>(deserializer: D) -> Result<SerializedOptional32, D::Error>
1089 where
1090 D: Deserializer<'de>,
1091 {
1092 struct SerializedOptional32Visitor;
1093 impl<'de> de::Visitor<'de> for SerializedOptional32Visitor {
1094 type Value = SerializedOptional32;
1095 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1096 write!(formatter, "integer")
1097 }
1098 fn visit_i64<E: de::Error>(self, v: i64) -> Result<SerializedOptional32, E> {
1099 Ok(match v {
1100 0 => SerializedOptional32::None,
1101 v if v < 0 => SerializedOptional32::Some(v as i32),
1102 v => SerializedOptional32::Some(v as i32 - 1),
1103 })
1104 }
1105 fn visit_u64<E: de::Error>(self, v: u64) -> Result<SerializedOptional32, E> {
1106 Ok(match v {
1107 0 => SerializedOptional32::None,
1108 v => SerializedOptional32::Some(v as i32 - 1),
1109 })
1110 }
1111 }
1112 deserializer.deserialize_any(SerializedOptional32Visitor)
1113 }
1114}
1115
1116#[derive(Clone, Debug)]
1117pub struct FunctionData {
1118 function_signature: String,
1119 param_names: Vec<String>,
1120}
1121
1122impl Serialize for FunctionData {
1123 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1124 where
1125 S: Serializer,
1126 {
1127 let mut seq = serializer.serialize_seq(None)?;
1128 seq.serialize_element(&self.function_signature)?;
1129 seq.serialize_element(&self.param_names)?;
1130 seq.end()
1131 }
1132}
1133
1134impl<'de> Deserialize<'de> for FunctionData {
1135 fn deserialize<D>(deserializer: D) -> Result<FunctionData, D::Error>
1136 where
1137 D: Deserializer<'de>,
1138 {
1139 struct FunctionDataVisitor;
1140 impl<'de> de::Visitor<'de> for FunctionDataVisitor {
1141 type Value = FunctionData;
1142 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1143 write!(formatter, "fn data")
1144 }
1145 fn visit_seq<A: de::SeqAccess<'de>>(self, mut v: A) -> Result<FunctionData, A::Error> {
1146 let function_signature: String = v
1147 .next_element()?
1148 .ok_or_else(|| A::Error::missing_field("function_signature"))?;
1149 let param_names: Vec<String> =
1150 v.next_element()?.ok_or_else(|| A::Error::missing_field("param_names"))?;
1151 Ok(FunctionData { function_signature, param_names })
1152 }
1153 }
1154 deserializer.deserialize_any(FunctionDataVisitor)
1155 }
1156}
561157
571158/// Builds the search index from the collected metadata
581159pub(crate) fn build_index(
591160 krate: &clean::Crate,
601161 cache: &mut Cache,
611162 tcx: TyCtxt<'_>,
62) -> SerializedSearchIndex {
63 // Maps from ID to position in the `crate_paths` array.
64 let mut itemid_to_pathid = FxHashMap::default();
65 let mut primitives = FxHashMap::default();
66 let mut associated_types = FxHashMap::default();
67
68 // item type, display path, re-exported internal path
69 let mut crate_paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)> = vec![];
1163 doc_root: &Path,
1164 resource_suffix: &str,
1165) -> Result<SerializedSearchIndex, Error> {
1166 let mut search_index = std::mem::take(&mut cache.search_index);
701167
711168 // Attach all orphan items to the type's definition if the type
721169 // has since been learned.
......@@ -74,15 +1171,15 @@ pub(crate) fn build_index(
741171 {
751172 if let Some((fqp, _)) = cache.paths.get(&parent) {
761173 let desc = short_markdown_summary(&item.doc_value(), &item.link_names(cache));
77 cache.search_index.push(IndexItem {
1174 search_index.push(IndexItem {
781175 ty: item.type_(),
791176 defid: item.item_id.as_def_id(),
801177 name: item.name.unwrap(),
81 path: join_path_syms(&fqp[..fqp.len() - 1]),
1178 module_path: fqp[..fqp.len() - 1].to_vec(),
821179 desc,
831180 parent: Some(parent),
841181 parent_idx: None,
85 exact_path: None,
1182 exact_module_path: None,
861183 impl_id,
871184 search_type: get_function_type_for_search(
881185 item,
......@@ -97,85 +1194,299 @@ pub(crate) fn build_index(
971194 }
981195 }
991196
1197 // Sort search index items. This improves the compressibility of the search index.
1198 search_index.sort_unstable_by(|k1, k2| {
1199 // `sort_unstable_by_key` produces lifetime errors
1200 // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too
1201 let k1 =
1202 (&k1.module_path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate)));
1203 let k2 =
1204 (&k2.module_path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate)));
1205 Ord::cmp(&k1, &k2)
1206 });
1207
1208 // Now, convert to an on-disk search index format
1209 //
1210 // if there's already a search index, load it into memory and add the new entries to it
1211 // otherwise, do nothing
1212 let mut serialized_index = SerializedSearchIndex::load(doc_root, resource_suffix)?;
1213
1214 // The crate always goes first in this list
1215 let crate_name = krate.name(tcx);
1001216 let crate_doc =
1011217 short_markdown_summary(&krate.module.doc_value(), &krate.module.link_names(cache));
1218 let crate_idx = {
1219 let crate_path = (ItemType::ExternCrate, vec![crate_name]);
1220 match serialized_index.crate_paths_index.entry(crate_path) {
1221 Entry::Occupied(index) => {
1222 let index = *index.get();
1223 serialized_index.descs[index] = crate_doc;
1224 for type_data in serialized_index.type_data.iter_mut() {
1225 if let Some(TypeData { inverted_function_signature_index, .. }) = type_data {
1226 for list in &mut inverted_function_signature_index[..] {
1227 list.retain(|fnid| {
1228 serialized_index.entry_data[usize::try_from(*fnid).unwrap()]
1229 .as_ref()
1230 .unwrap()
1231 .krate
1232 != index
1233 });
1234 }
1235 }
1236 }
1237 for i in (index + 1)..serialized_index.entry_data.len() {
1238 // if this crate has been built before, replace its stuff with new
1239 if let Some(EntryData { krate, .. }) = serialized_index.entry_data[i]
1240 && krate == index
1241 {
1242 serialized_index.entry_data[i] = None;
1243 serialized_index.descs[i] = String::new();
1244 serialized_index.function_data[i] = None;
1245 if serialized_index.path_data[i].is_none() {
1246 serialized_index.names[i] = String::new();
1247 }
1248 }
1249 if let Some(alias_pointer) = serialized_index.alias_pointers[i]
1250 && serialized_index.entry_data[alias_pointer].is_none()
1251 {
1252 serialized_index.alias_pointers[i] = None;
1253 if serialized_index.path_data[i].is_none()
1254 && serialized_index.entry_data[i].is_none()
1255 {
1256 serialized_index.names[i] = String::new();
1257 }
1258 }
1259 }
1260 index
1261 }
1262 Entry::Vacant(slot) => {
1263 let krate = serialized_index.names.len();
1264 slot.insert(krate);
1265 serialized_index.push(
1266 crate_name.as_str().to_string(),
1267 Some(PathData {
1268 ty: ItemType::ExternCrate,
1269 module_path: vec![],
1270 exact_module_path: None,
1271 }),
1272 Some(EntryData {
1273 krate,
1274 ty: ItemType::ExternCrate,
1275 module_path: None,
1276 exact_module_path: None,
1277 parent: None,
1278 deprecated: false,
1279 associated_item_disambiguator: None,
1280 }),
1281 crate_doc,
1282 None,
1283 None,
1284 None,
1285 );
1286 krate
1287 }
1288 }
1289 };
1290
1291 // First, populate associated item parents
1292 let crate_items: Vec<&mut IndexItem> = search_index
1293 .iter_mut()
1294 .map(|item| {
1295 item.parent_idx = item.parent.and_then(|defid| {
1296 cache.paths.get(&defid).map(|&(ref fqp, ty)| {
1297 let pathid = serialized_index.names.len();
1298 match serialized_index.crate_paths_index.entry((ty, fqp.clone())) {
1299 Entry::Occupied(entry) => *entry.get(),
1300 Entry::Vacant(entry) => {
1301 entry.insert(pathid);
1302 let (name, path) = fqp.split_last().unwrap();
1303 serialized_index.push_path(
1304 name.as_str().to_string(),
1305 PathData {
1306 ty,
1307 module_path: path.to_vec(),
1308 exact_module_path: if let Some(exact_path) =
1309 cache.exact_paths.get(&defid)
1310 && let Some((name2, exact_path)) = exact_path.split_last()
1311 && name == name2
1312 {
1313 Some(exact_path.to_vec())
1314 } else {
1315 None
1316 },
1317 },
1318 );
1319 usize::try_from(pathid).unwrap()
1320 }
1321 }
1322 })
1323 });
1324
1325 if let Some(defid) = item.defid
1326 && item.parent_idx.is_none()
1327 {
1328 // If this is a re-export, retain the original path.
1329 // Associated items don't use this.
1330 // Their parent carries the exact fqp instead.
1331 let exact_fqp = cache
1332 .exact_paths
1333 .get(&defid)
1334 .or_else(|| cache.external_paths.get(&defid).map(|(fqp, _)| fqp));
1335 item.exact_module_path = exact_fqp.and_then(|fqp| {
1336 // Re-exports only count if the name is exactly the same.
1337 // This is a size optimization, since it means we only need
1338 // to store the name once (and the path is re-used for everything
1339 // exported from this same module). It's also likely to Do
1340 // What I Mean, since if a re-export changes the name, it might
1341 // also be a change in semantic meaning.
1342 if fqp.last() != Some(&item.name) {
1343 return None;
1344 }
1345 let path =
1346 if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
1347 // `#[macro_export]` always exports to the crate root.
1348 vec![tcx.crate_name(defid.krate)]
1349 } else {
1350 if fqp.len() < 2 {
1351 return None;
1352 }
1353 fqp[..fqp.len() - 1].to_vec()
1354 };
1355 if path == item.module_path {
1356 return None;
1357 }
1358 Some(path)
1359 });
1360 } else if let Some(parent_idx) = item.parent_idx {
1361 let i = usize::try_from(parent_idx).unwrap();
1362 item.module_path =
1363 serialized_index.path_data[i].as_ref().unwrap().module_path.clone();
1364 item.exact_module_path =
1365 serialized_index.path_data[i].as_ref().unwrap().exact_module_path.clone();
1366 }
1021367
103 #[derive(Eq, Ord, PartialEq, PartialOrd)]
104 struct SerSymbolAsStr(Symbol);
1368 &mut *item
1369 })
1370 .collect();
1051371
106 impl Serialize for SerSymbolAsStr {
107 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
108 where
109 S: Serializer,
1372 // Now, find anywhere that the same name is used for two different items
1373 // these need a disambiguator hash for lints
1374 let mut associated_item_duplicates = FxHashMap::<(usize, ItemType, Symbol), usize>::default();
1375 for item in crate_items.iter().map(|x| &*x) {
1376 if item.impl_id.is_some()
1377 && let Some(parent_idx) = item.parent_idx
1101378 {
111 self.0.as_str().serialize(serializer)
1379 let count =
1380 associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0);
1381 *count += 1;
1121382 }
1131383 }
1141384
115 type AliasMap = BTreeMap<SerSymbolAsStr, Vec<usize>>;
116 // Aliases added through `#[doc(alias = "...")]`. Since a few items can have the same alias,
117 // we need the alias element to have an array of items.
118 let mut aliases: AliasMap = BTreeMap::new();
1385 // now populate the actual entries, type data, and function data
1386 for item in crate_items {
1387 assert_eq!(
1388 item.parent.is_some(),
1389 item.parent_idx.is_some(),
1390 "`{}` is missing idx",
1391 item.name
1392 );
1191393
120 // Sort search index items. This improves the compressibility of the search index.
121 cache.search_index.sort_unstable_by(|k1, k2| {
122 // `sort_unstable_by_key` produces lifetime errors
123 // HACK(rustdoc): should not be sorting `CrateNum` or `DefIndex`, this will soon go away, too
124 let k1 = (&k1.path, k1.name.as_str(), &k1.ty, k1.parent.map(|id| (id.index, id.krate)));
125 let k2 = (&k2.path, k2.name.as_str(), &k2.ty, k2.parent.map(|id| (id.index, id.krate)));
126 Ord::cmp(&k1, &k2)
127 });
1394 let module_path = Some(serialized_index.get_id_by_module_path(&item.module_path));
1395 let exact_module_path = item
1396 .exact_module_path
1397 .as_ref()
1398 .map(|path| serialized_index.get_id_by_module_path(path));
1399
1400 let new_entry_id = serialized_index.push(
1401 item.name.as_str().to_string(),
1402 None,
1403 Some(EntryData {
1404 ty: item.ty,
1405 parent: item.parent_idx,
1406 module_path,
1407 exact_module_path,
1408 deprecated: item.deprecation.is_some(),
1409 associated_item_disambiguator: if let Some(impl_id) = item.impl_id
1410 && let Some(parent_idx) = item.parent_idx
1411 && associated_item_duplicates
1412 .get(&(parent_idx, item.ty, item.name))
1413 .copied()
1414 .unwrap_or(0)
1415 > 1
1416 {
1417 Some(render::get_id_for_impl(tcx, ItemId::DefId(impl_id)))
1418 } else {
1419 None
1420 },
1421 krate: crate_idx,
1422 }),
1423 item.desc.to_string(),
1424 None, // filled in after all the types have been indexed
1425 None,
1426 None,
1427 );
1281428
129 // Set up alias indexes.
130 for (i, item) in cache.search_index.iter().enumerate() {
1429 // Aliases
1430 // -------
1311431 for alias in &item.aliases[..] {
132 aliases.entry(SerSymbolAsStr(*alias)).or_default().push(i);
1432 serialized_index.push_alias(alias.as_str().to_string(), new_entry_id);
1331433 }
134 }
135
136 // Reduce `DefId` in paths into smaller sequential numbers,
137 // and prune the paths that do not appear in the index.
138 let mut lastpath = "";
139 let mut lastpathid = 0isize;
1401434
141 // First, on function signatures
142 let mut search_index = std::mem::take(&mut cache.search_index);
143 for item in search_index.iter_mut() {
144 fn insert_into_map<F: std::hash::Hash + Eq>(
145 map: &mut FxHashMap<F, isize>,
146 itemid: F,
147 lastpathid: &mut isize,
148 crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
149 item_type: ItemType,
1435 // Function signature reverse index
1436 // --------------------------------
1437 fn insert_into_map(
1438 ty: ItemType,
1501439 path: &[Symbol],
1511440 exact_path: Option<&[Symbol]>,
1521441 search_unbox: bool,
1442 serialized_index: &mut SerializedSearchIndex,
1443 used_in_function_signature: &mut BTreeSet<isize>,
1531444 ) -> RenderTypeId {
154 match map.entry(itemid) {
155 Entry::Occupied(entry) => RenderTypeId::Index(*entry.get()),
1445 let pathid = serialized_index.names.len();
1446 let pathid = match serialized_index.crate_paths_index.entry((ty, path.to_vec())) {
1447 Entry::Occupied(entry) => {
1448 let id = *entry.get();
1449 if serialized_index.type_data[id].as_mut().is_none() {
1450 serialized_index.type_data[id] = Some(TypeData {
1451 search_unbox,
1452 inverted_function_signature_index: Vec::new(),
1453 });
1454 } else if search_unbox {
1455 serialized_index.type_data[id].as_mut().unwrap().search_unbox = true;
1456 }
1457 id
1458 }
1561459 Entry::Vacant(entry) => {
157 let pathid = *lastpathid;
1581460 entry.insert(pathid);
159 *lastpathid += 1;
160 crate_paths.push((
161 item_type,
162 path.to_vec(),
163 exact_path.map(|path| path.to_vec()),
164 search_unbox,
165 ));
166 RenderTypeId::Index(pathid)
1461 let (name, path) = path.split_last().unwrap();
1462 serialized_index.push_type(
1463 name.to_string(),
1464 PathData {
1465 ty,
1466 module_path: path.to_vec(),
1467 exact_module_path: if let Some(exact_path) = exact_path
1468 && let Some((name2, exact_path)) = exact_path.split_last()
1469 && name == name2
1470 {
1471 Some(exact_path.to_vec())
1472 } else {
1473 None
1474 },
1475 },
1476 TypeData { search_unbox, inverted_function_signature_index: Vec::new() },
1477 );
1478 pathid
1671479 }
168 }
1480 };
1481 used_in_function_signature.insert(isize::try_from(pathid).unwrap());
1482 RenderTypeId::Index(isize::try_from(pathid).unwrap())
1691483 }
1701484
1711485 fn convert_render_type_id(
1721486 id: RenderTypeId,
1731487 cache: &mut Cache,
174 itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
175 primitives: &mut FxHashMap<Symbol, isize>,
176 associated_types: &mut FxHashMap<Symbol, isize>,
177 lastpathid: &mut isize,
178 crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
1488 serialized_index: &mut SerializedSearchIndex,
1489 used_in_function_signature: &mut BTreeSet<isize>,
1791490 tcx: TyCtxt<'_>,
1801491 ) -> Option<RenderTypeId> {
1811492 use crate::clean::PrimitiveType;
......@@ -192,39 +1503,55 @@ pub(crate) fn build_index(
1921503 };
1931504 match id {
1941505 RenderTypeId::Mut => Some(insert_into_map(
195 primitives,
196 kw::Mut,
197 lastpathid,
198 crate_paths,
1991506 ItemType::Keyword,
2001507 &[kw::Mut],
2011508 None,
2021509 search_unbox,
1510 serialized_index,
1511 used_in_function_signature,
2031512 )),
2041513 RenderTypeId::DefId(defid) => {
2051514 if let Some(&(ref fqp, item_type)) =
2061515 paths.get(&defid).or_else(|| external_paths.get(&defid))
2071516 {
208 let exact_fqp = exact_paths
209 .get(&defid)
210 .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
211 // Re-exports only count if the name is exactly the same.
212 // This is a size optimization, since it means we only need
213 // to store the name once (and the path is re-used for everything
214 // exported from this same module). It's also likely to Do
215 // What I Mean, since if a re-export changes the name, it might
216 // also be a change in semantic meaning.
217 .filter(|this_fqp| this_fqp.last() == fqp.last());
218 Some(insert_into_map(
219 itemid_to_pathid,
220 ItemId::DefId(defid),
221 lastpathid,
222 crate_paths,
223 item_type,
224 fqp,
225 exact_fqp.map(|x| &x[..]).filter(|exact_fqp| exact_fqp != fqp),
226 search_unbox,
227 ))
1517 if tcx.lang_items().fn_mut_trait() == Some(defid)
1518 || tcx.lang_items().fn_once_trait() == Some(defid)
1519 || tcx.lang_items().fn_trait() == Some(defid)
1520 {
1521 let name = *fqp.last().unwrap();
1522 // Make absolutely sure we use this single, correct path,
1523 // because search.js needs to match. If we don't do this,
1524 // there are three different paths that these traits may
1525 // appear to come from.
1526 Some(insert_into_map(
1527 item_type,
1528 &[sym::core, sym::ops, name],
1529 Some(&[sym::core, sym::ops, name]),
1530 search_unbox,
1531 serialized_index,
1532 used_in_function_signature,
1533 ))
1534 } else {
1535 let exact_fqp = exact_paths
1536 .get(&defid)
1537 .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
1538 .map(|v| &v[..])
1539 // Re-exports only count if the name is exactly the same.
1540 // This is a size optimization, since it means we only need
1541 // to store the name once (and the path is re-used for everything
1542 // exported from this same module). It's also likely to Do
1543 // What I Mean, since if a re-export changes the name, it might
1544 // also be a change in semantic meaning.
1545 .filter(|this_fqp| this_fqp.last() == fqp.last());
1546 Some(insert_into_map(
1547 item_type,
1548 fqp,
1549 exact_fqp,
1550 search_unbox,
1551 serialized_index,
1552 used_in_function_signature,
1553 ))
1554 }
2281555 } else {
2291556 None
2301557 }
......@@ -232,26 +1559,25 @@ pub(crate) fn build_index(
2321559 RenderTypeId::Primitive(primitive) => {
2331560 let sym = primitive.as_sym();
2341561 Some(insert_into_map(
235 primitives,
236 sym,
237 lastpathid,
238 crate_paths,
2391562 ItemType::Primitive,
2401563 &[sym],
2411564 None,
2421565 search_unbox,
1566 serialized_index,
1567 used_in_function_signature,
2431568 ))
2441569 }
245 RenderTypeId::Index(_) => Some(id),
1570 RenderTypeId::Index(index) => {
1571 used_in_function_signature.insert(index);
1572 Some(id)
1573 }
2461574 RenderTypeId::AssociatedType(sym) => Some(insert_into_map(
247 associated_types,
248 sym,
249 lastpathid,
250 crate_paths,
2511575 ItemType::AssocType,
2521576 &[sym],
2531577 None,
2541578 search_unbox,
1579 serialized_index,
1580 used_in_function_signature,
2551581 )),
2561582 }
2571583 }
......@@ -259,11 +1585,8 @@ pub(crate) fn build_index(
2591585 fn convert_render_type(
2601586 ty: &mut RenderType,
2611587 cache: &mut Cache,
262 itemid_to_pathid: &mut FxHashMap<ItemId, isize>,
263 primitives: &mut FxHashMap<Symbol, isize>,
264 associated_types: &mut FxHashMap<Symbol, isize>,
265 lastpathid: &mut isize,
266 crate_paths: &mut Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
1588 serialized_index: &mut SerializedSearchIndex,
1589 used_in_function_signature: &mut BTreeSet<isize>,
2671590 tcx: TyCtxt<'_>,
2681591 ) {
2691592 if let Some(generics) = &mut ty.generics {
......@@ -271,11 +1594,8 @@ pub(crate) fn build_index(
2711594 convert_render_type(
2721595 item,
2731596 cache,
274 itemid_to_pathid,
275 primitives,
276 associated_types,
277 lastpathid,
278 crate_paths,
1597 serialized_index,
1598 used_in_function_signature,
2791599 tcx,
2801600 );
2811601 }
......@@ -285,11 +1605,8 @@ pub(crate) fn build_index(
2851605 let converted_associated_type = convert_render_type_id(
2861606 *associated_type,
2871607 cache,
288 itemid_to_pathid,
289 primitives,
290 associated_types,
291 lastpathid,
292 crate_paths,
1608 serialized_index,
1609 used_in_function_signature,
2931610 tcx,
2941611 );
2951612 let Some(converted_associated_type) = converted_associated_type else {
......@@ -300,11 +1617,8 @@ pub(crate) fn build_index(
3001617 convert_render_type(
3011618 constraint,
3021619 cache,
303 itemid_to_pathid,
304 primitives,
305 associated_types,
306 lastpathid,
307 crate_paths,
1620 serialized_index,
1621 used_in_function_signature,
3081622 tcx,
3091623 );
3101624 }
......@@ -318,24 +1632,74 @@ pub(crate) fn build_index(
3181632 ty.id = convert_render_type_id(
3191633 id,
3201634 cache,
321 itemid_to_pathid,
322 primitives,
323 associated_types,
324 lastpathid,
325 crate_paths,
1635 serialized_index,
1636 used_in_function_signature,
3261637 tcx,
3271638 );
1639 use crate::clean::PrimitiveType;
1640 // These cases are added to the inverted index, but not actually included
1641 // in the signature. There's a matching set of cases in the
1642 // `unifyFunctionTypeIsMatchCandidate` function, for the slow path.
1643 match id {
1644 // typeNameIdOfArrayOrSlice
1645 RenderTypeId::Primitive(PrimitiveType::Array | PrimitiveType::Slice) => {
1646 insert_into_map(
1647 ItemType::Primitive,
1648 &[Symbol::intern("[]")],
1649 None,
1650 false,
1651 serialized_index,
1652 used_in_function_signature,
1653 );
1654 }
1655 RenderTypeId::Primitive(PrimitiveType::Tuple | PrimitiveType::Unit) => {
1656 // typeNameIdOfArrayOrSlice
1657 insert_into_map(
1658 ItemType::Primitive,
1659 &[Symbol::intern("()")],
1660 None,
1661 false,
1662 serialized_index,
1663 used_in_function_signature,
1664 );
1665 }
1666 // typeNameIdOfHof
1667 RenderTypeId::Primitive(PrimitiveType::Fn) => {
1668 insert_into_map(
1669 ItemType::Primitive,
1670 &[Symbol::intern("->")],
1671 None,
1672 false,
1673 serialized_index,
1674 used_in_function_signature,
1675 );
1676 }
1677 RenderTypeId::DefId(did)
1678 if tcx.lang_items().fn_mut_trait() == Some(did)
1679 || tcx.lang_items().fn_once_trait() == Some(did)
1680 || tcx.lang_items().fn_trait() == Some(did) =>
1681 {
1682 insert_into_map(
1683 ItemType::Primitive,
1684 &[Symbol::intern("->")],
1685 None,
1686 false,
1687 serialized_index,
1688 used_in_function_signature,
1689 );
1690 }
1691 // not special
1692 _ => {}
1693 }
3281694 }
3291695 if let Some(search_type) = &mut item.search_type {
1696 let mut used_in_function_signature = BTreeSet::new();
3301697 for item in &mut search_type.inputs {
3311698 convert_render_type(
3321699 item,
3331700 cache,
334 &mut itemid_to_pathid,
335 &mut primitives,
336 &mut associated_types,
337 &mut lastpathid,
338 &mut crate_paths,
1701 &mut serialized_index,
1702 &mut used_in_function_signature,
3391703 tcx,
3401704 );
3411705 }
......@@ -343,11 +1707,8 @@ pub(crate) fn build_index(
3431707 convert_render_type(
3441708 item,
3451709 cache,
346 &mut itemid_to_pathid,
347 &mut primitives,
348 &mut associated_types,
349 &mut lastpathid,
350 &mut crate_paths,
1710 &mut serialized_index,
1711 &mut used_in_function_signature,
3511712 tcx,
3521713 );
3531714 }
......@@ -356,464 +1717,56 @@ pub(crate) fn build_index(
3561717 convert_render_type(
3571718 trait_,
3581719 cache,
359 &mut itemid_to_pathid,
360 &mut primitives,
361 &mut associated_types,
362 &mut lastpathid,
363 &mut crate_paths,
1720 &mut serialized_index,
1721 &mut used_in_function_signature,
3641722 tcx,
3651723 );
3661724 }
3671725 }
368 }
369 }
370
371 let Cache { ref paths, ref exact_paths, ref external_paths, .. } = *cache;
372
373 // Then, on parent modules
374 let crate_items: Vec<&IndexItem> = search_index
375 .iter_mut()
376 .map(|item| {
377 item.parent_idx =
378 item.parent.and_then(|defid| match itemid_to_pathid.entry(ItemId::DefId(defid)) {
379 Entry::Occupied(entry) => Some(*entry.get()),
380 Entry::Vacant(entry) => {
381 let pathid = lastpathid;
382 entry.insert(pathid);
383 lastpathid += 1;
384
385 if let Some(&(ref fqp, short)) = paths.get(&defid) {
386 let exact_fqp = exact_paths
387 .get(&defid)
388 .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp))
389 .filter(|exact_fqp| {
390 exact_fqp.last() == Some(&item.name) && *exact_fqp != fqp
391 });
392 crate_paths.push((
393 short,
394 fqp.clone(),
395 exact_fqp.cloned(),
396 utils::has_doc_flag(tcx, defid, sym::search_unbox),
397 ));
398 Some(pathid)
399 } else {
400 None
401 }
402 }
403 });
404
405 if let Some(defid) = item.defid
406 && item.parent_idx.is_none()
407 {
408 // If this is a re-export, retain the original path.
409 // Associated items don't use this.
410 // Their parent carries the exact fqp instead.
411 let exact_fqp = exact_paths
412 .get(&defid)
413 .or_else(|| external_paths.get(&defid).map(|(fqp, _)| fqp));
414 item.exact_path = exact_fqp.and_then(|fqp| {
415 // Re-exports only count if the name is exactly the same.
416 // This is a size optimization, since it means we only need
417 // to store the name once (and the path is re-used for everything
418 // exported from this same module). It's also likely to Do
419 // What I Mean, since if a re-export changes the name, it might
420 // also be a change in semantic meaning.
421 if fqp.last() != Some(&item.name) {
422 return None;
423 }
424 let path =
425 if item.ty == ItemType::Macro && tcx.has_attr(defid, sym::macro_export) {
426 // `#[macro_export]` always exports to the crate root.
427 tcx.crate_name(defid.krate).to_string()
428 } else {
429 if fqp.len() < 2 {
430 return None;
431 }
432 join_path_syms(&fqp[..fqp.len() - 1])
433 };
434 if path == item.path {
435 return None;
436 }
437 Some(path)
438 });
439 } else if let Some(parent_idx) = item.parent_idx {
440 let i = <isize as TryInto<usize>>::try_into(parent_idx).unwrap();
441 item.path = {
442 let p = &crate_paths[i].1;
443 join_path_syms(&p[..p.len() - 1])
444 };
445 item.exact_path =
446 crate_paths[i].2.as_ref().map(|xp| join_path_syms(&xp[..xp.len() - 1]));
447 }
448
449 // Omit the parent path if it is same to that of the prior item.
450 if lastpath == item.path {
451 item.path.clear();
452 } else {
453 lastpath = &item.path;
454 }
455
456 &*item
457 })
458 .collect();
459
460 // Find associated items that need disambiguators
461 let mut associated_item_duplicates = FxHashMap::<(isize, ItemType, Symbol), usize>::default();
462
463 for &item in &crate_items {
464 if item.impl_id.is_some()
465 && let Some(parent_idx) = item.parent_idx
466 {
467 let count =
468 associated_item_duplicates.entry((parent_idx, item.ty, item.name)).or_insert(0);
469 *count += 1;
470 }
471 }
472
473 let associated_item_disambiguators = crate_items
474 .iter()
475 .enumerate()
476 .filter_map(|(index, item)| {
477 let impl_id = ItemId::DefId(item.impl_id?);
478 let parent_idx = item.parent_idx?;
479 let count = *associated_item_duplicates.get(&(parent_idx, item.ty, item.name))?;
480 if count > 1 { Some((index, render::get_id_for_impl(tcx, impl_id))) } else { None }
481 })
482 .collect::<Vec<_>>();
483
484 struct CrateData<'a> {
485 items: Vec<&'a IndexItem>,
486 paths: Vec<(ItemType, Vec<Symbol>, Option<Vec<Symbol>>, bool)>,
487 // The String is alias name and the vec is the list of the elements with this alias.
488 //
489 // To be noted: the `usize` elements are indexes to `items`.
490 aliases: &'a AliasMap,
491 // Used when a type has more than one impl with an associated item with the same name.
492 associated_item_disambiguators: &'a Vec<(usize, String)>,
493 // A list of shard lengths encoded as vlqhex. See the comment in write_vlqhex_to_string
494 // for information on the format.
495 desc_index: String,
496 // A list of items with no description. This is eventually turned into a bitmap.
497 empty_desc: Vec<u32>,
498 }
499
500 struct Paths {
501 ty: ItemType,
502 name: Symbol,
503 path: Option<usize>,
504 exact_path: Option<usize>,
505 search_unbox: bool,
506 }
507
508 impl Serialize for Paths {
509 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
510 where
511 S: Serializer,
512 {
513 let mut seq = serializer.serialize_seq(None)?;
514 seq.serialize_element(&self.ty)?;
515 seq.serialize_element(self.name.as_str())?;
516 if let Some(ref path) = self.path {
517 seq.serialize_element(path)?;
518 }
519 if let Some(ref path) = self.exact_path {
520 assert!(self.path.is_some());
521 seq.serialize_element(path)?;
522 }
523 if self.search_unbox {
524 if self.path.is_none() {
525 seq.serialize_element(&None::<u8>)?;
526 }
527 if self.exact_path.is_none() {
528 seq.serialize_element(&None::<u8>)?;
529 }
530 seq.serialize_element(&1)?;
531 }
532 seq.end()
533 }
534 }
535
536 impl Serialize for CrateData<'_> {
537 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
538 where
539 S: Serializer,
540 {
541 let mut extra_paths = FxHashMap::default();
542 // We need to keep the order of insertion, hence why we use an `IndexMap`. Then we will
543 // insert these "extra paths" (which are paths of items from external crates) into the
544 // `full_paths` list at the end.
545 let mut revert_extra_paths = FxIndexMap::default();
546 let mut mod_paths = FxHashMap::default();
547 for (index, item) in self.items.iter().enumerate() {
548 if item.path.is_empty() {
549 continue;
550 }
551 mod_paths.insert(&item.path, index);
552 }
553 let mut paths = Vec::with_capacity(self.paths.len());
554 for &(ty, ref path, ref exact, search_unbox) in &self.paths {
555 if path.len() < 2 {
556 paths.push(Paths {
557 ty,
558 name: path[0],
559 path: None,
560 exact_path: None,
561 search_unbox,
562 });
563 continue;
564 }
565 let full_path = join_path_syms(&path[..path.len() - 1]);
566 let full_exact_path = exact
567 .as_ref()
568 .filter(|exact| exact.last() == path.last() && exact.len() >= 2)
569 .map(|exact| join_path_syms(&exact[..exact.len() - 1]));
570 let exact_path = extra_paths.len() + self.items.len();
571 let exact_path = full_exact_path.as_ref().map(|full_exact_path| match extra_paths
572 .entry(full_exact_path.clone())
573 {
574 Entry::Occupied(entry) => *entry.get(),
575 Entry::Vacant(entry) => {
576 if let Some(index) = mod_paths.get(&full_exact_path) {
577 return *index;
578 }
579 entry.insert(exact_path);
580 if !revert_extra_paths.contains_key(&exact_path) {
581 revert_extra_paths.insert(exact_path, full_exact_path.clone());
582 }
583 exact_path
584 }
585 });
586 if let Some(index) = mod_paths.get(&full_path) {
587 paths.push(Paths {
588 ty,
589 name: *path.last().unwrap(),
590 path: Some(*index),
591 exact_path,
592 search_unbox,
593 });
594 continue;
595 }
596 // It means it comes from an external crate so the item and its path will be
597 // stored into another array.
1726 let search_type_size = search_type.size() +
1727 // Artificially give struct fields a size of 8 instead of their real
1728 // size of 2. This is because search.js sorts them to the end, so
1729 // by pushing them down, we prevent them from blocking real 2-arity functions.
5981730 //
599 // `index` is put after the last `mod_paths`
600 let index = extra_paths.len() + self.items.len();
601 match extra_paths.entry(full_path.clone()) {
602 Entry::Occupied(entry) => {
603 paths.push(Paths {
604 ty,
605 name: *path.last().unwrap(),
606 path: Some(*entry.get()),
607 exact_path,
608 search_unbox,
609 });
610 }
611 Entry::Vacant(entry) => {
612 entry.insert(index);
613 if !revert_extra_paths.contains_key(&index) {
614 revert_extra_paths.insert(index, full_path);
615 }
616 paths.push(Paths {
617 ty,
618 name: *path.last().unwrap(),
619 path: Some(index),
620 exact_path,
621 search_unbox,
622 });
623 }
624 }
625 }
626
627 // Direct exports use adjacent arrays for the current crate's items,
628 // but re-exported exact paths don't.
629 let mut re_exports = Vec::new();
630 for (item_index, item) in self.items.iter().enumerate() {
631 if let Some(exact_path) = item.exact_path.as_ref() {
632 if let Some(path_index) = mod_paths.get(&exact_path) {
633 re_exports.push((item_index, *path_index));
634 } else {
635 let path_index = extra_paths.len() + self.items.len();
636 let path_index = match extra_paths.entry(exact_path.clone()) {
637 Entry::Occupied(entry) => *entry.get(),
638 Entry::Vacant(entry) => {
639 entry.insert(path_index);
640 if !revert_extra_paths.contains_key(&path_index) {
641 revert_extra_paths.insert(path_index, exact_path.clone());
642 }
643 path_index
644 }
645 };
646 re_exports.push((item_index, path_index));
647 }
648 }
649 }
650
651 let mut names = Vec::with_capacity(self.items.len());
652 let mut types = String::with_capacity(self.items.len());
653 let mut full_paths = Vec::with_capacity(self.items.len());
654 let mut parents = String::with_capacity(self.items.len());
655 let mut parents_backref_queue = VecDeque::new();
656 let mut functions = String::with_capacity(self.items.len());
657 let mut deprecated = Vec::with_capacity(self.items.len());
658
659 let mut type_backref_queue = VecDeque::new();
660
661 let mut last_name = None;
662 for (index, item) in self.items.iter().enumerate() {
663 let n = item.ty as u8;
664 let c = char::from(n + b'A');
665 assert!(c <= 'z', "item types must fit within ASCII printables");
666 types.push(c);
667
668 assert_eq!(
669 item.parent.is_some(),
670 item.parent_idx.is_some(),
671 "`{}` is missing idx",
672 item.name
673 );
674 assert!(
675 parents_backref_queue.len() <= 16,
676 "the string encoding only supports 16 slots of lookback"
677 );
678 let parent: i32 = item.parent_idx.map(|x| x + 1).unwrap_or(0).try_into().unwrap();
679 if let Some(idx) = parents_backref_queue.iter().position(|p: &i32| *p == parent) {
680 parents.push(
681 char::try_from('0' as u32 + u32::try_from(idx).unwrap())
682 .expect("last possible value is '?'"),
683 );
684 } else if parent == 0 {
685 write_vlqhex_to_string(parent, &mut parents);
686 } else {
687 parents_backref_queue.push_front(parent);
688 write_vlqhex_to_string(parent, &mut parents);
689 if parents_backref_queue.len() > 16 {
690 parents_backref_queue.pop_back();
691 }
692 }
693
694 if Some(item.name.as_str()) == last_name {
695 names.push("");
1731 // The number 8 is arbitrary. We want it big, but not enormous,
1732 // because the postings list has to fill in an empty array for each
1733 // unoccupied size.
1734 if item.ty.is_fn_like() { 0 } else { 16 };
1735 serialized_index.function_data[new_entry_id] = Some(FunctionData {
1736 function_signature: {
1737 let mut function_signature = String::new();
1738 search_type.write_to_string_without_param_names(&mut function_signature);
1739 function_signature
1740 },
1741 param_names: search_type
1742 .param_names
1743 .iter()
1744 .map(|sym| sym.map(|sym| sym.to_string()).unwrap_or(String::new()))
1745 .collect::<Vec<String>>(),
1746 });
1747 for index in used_in_function_signature {
1748 let postings = if index >= 0 {
1749 assert!(serialized_index.path_data[index as usize].is_some());
1750 &mut serialized_index.type_data[index as usize]
1751 .as_mut()
1752 .unwrap()
1753 .inverted_function_signature_index
6961754 } else {
697 names.push(item.name.as_str());
698 last_name = Some(item.name.as_str());
699 }
700
701 if !item.path.is_empty() {
702 full_paths.push((index, &item.path));
703 }
704
705 match &item.search_type {
706 Some(ty) => ty.write_to_string(&mut functions, &mut type_backref_queue),
707 None => functions.push('`'),
708 }
709
710 if item.deprecation.is_some() {
711 // bitmasks always use 1-indexing for items, with 0 as the crate itself
712 deprecated.push(u32::try_from(index + 1).unwrap());
713 }
714 }
715
716 for (index, path) in &revert_extra_paths {
717 full_paths.push((*index, path));
718 }
719
720 let param_names: Vec<(usize, String)> = {
721 let mut prev = Vec::new();
722 let mut result = Vec::new();
723 for (index, item) in self.items.iter().enumerate() {
724 if let Some(ty) = &item.search_type
725 && let my = ty
726 .param_names
727 .iter()
728 .filter_map(|sym| sym.map(|sym| sym.to_string()))
729 .collect::<Vec<_>>()
730 && my != prev
731 {
732 result.push((index, my.join(",")));
733 prev = my;
1755 let generic_id = usize::try_from(-index).unwrap() - 1;
1756 for _ in serialized_index.generic_inverted_index.len()..=generic_id {
1757 serialized_index.generic_inverted_index.push(Vec::new());
7341758 }
1759 &mut serialized_index.generic_inverted_index[generic_id]
1760 };
1761 while postings.len() <= search_type_size {
1762 postings.push(Vec::new());
7351763 }
736 result
737 };
738
739 let has_aliases = !self.aliases.is_empty();
740 let mut crate_data =
741 serializer.serialize_struct("CrateData", if has_aliases { 13 } else { 12 })?;
742 crate_data.serialize_field("t", &types)?;
743 crate_data.serialize_field("n", &names)?;
744 crate_data.serialize_field("q", &full_paths)?;
745 crate_data.serialize_field("i", &parents)?;
746 crate_data.serialize_field("f", &functions)?;
747 crate_data.serialize_field("D", &self.desc_index)?;
748 crate_data.serialize_field("p", &paths)?;
749 crate_data.serialize_field("r", &re_exports)?;
750 crate_data.serialize_field("b", &self.associated_item_disambiguators)?;
751 crate_data.serialize_field("c", &bitmap_to_string(&deprecated))?;
752 crate_data.serialize_field("e", &bitmap_to_string(&self.empty_desc))?;
753 crate_data.serialize_field("P", &param_names)?;
754 if has_aliases {
755 crate_data.serialize_field("a", &self.aliases)?;
1764 postings[search_type_size].push(new_entry_id as u32);
7561765 }
757 crate_data.end()
7581766 }
7591767 }
7601768
761 let (empty_desc, desc) = {
762 let mut empty_desc = Vec::new();
763 let mut result = Vec::new();
764 let mut set = String::new();
765 let mut len: usize = 0;
766 let mut item_index: u32 = 0;
767 for desc in std::iter::once(&crate_doc).chain(crate_items.iter().map(|item| &item.desc)) {
768 if desc.is_empty() {
769 empty_desc.push(item_index);
770 item_index += 1;
771 continue;
772 }
773 if set.len() >= DESC_INDEX_SHARD_LEN {
774 result.push((len, std::mem::take(&mut set)));
775 len = 0;
776 } else if len != 0 {
777 set.push('\n');
778 }
779 set.push_str(desc);
780 len += 1;
781 item_index += 1;
782 }
783 result.push((len, std::mem::take(&mut set)));
784 (empty_desc, result)
785 };
786
787 let desc_index = {
788 let mut desc_index = String::with_capacity(desc.len() * 4);
789 for &(len, _) in desc.iter() {
790 write_vlqhex_to_string(len.try_into().unwrap(), &mut desc_index);
791 }
792 desc_index
793 };
794
795 assert_eq!(
796 crate_items.len() + 1,
797 desc.iter().map(|(len, _)| *len).sum::<usize>() + empty_desc.len()
798 );
799
800 // The index, which is actually used to search, is JSON
801 // It uses `JSON.parse(..)` to actually load, since JSON
802 // parses faster than the full JavaScript syntax.
803 let crate_name = krate.name(tcx);
804 let data = CrateData {
805 items: crate_items,
806 paths: crate_paths,
807 aliases: &aliases,
808 associated_item_disambiguators: &associated_item_disambiguators,
809 desc_index,
810 empty_desc,
811 };
812 let index = OrderedJson::array_unsorted([
813 OrderedJson::serialize(crate_name.as_str()).unwrap(),
814 OrderedJson::serialize(data).unwrap(),
815 ]);
816 SerializedSearchIndex { index, desc }
1769 Ok(serialized_index.sort())
8171770}
8181771
8191772pub(crate) fn get_function_type_for_search(
src/librustdoc/html/render/search_index/encode.rs+51-193
......@@ -1,6 +1,4 @@
1use base64::prelude::*;
2
3pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
1pub(crate) fn write_signed_vlqhex_to_string(n: i32, string: &mut String) {
42 let (sign, magnitude): (bool, u32) =
53 if n >= 0 { (false, n.try_into().unwrap()) } else { (true, (-n).try_into().unwrap()) };
64 // zig-zag encoding
......@@ -37,206 +35,66 @@ pub(crate) fn write_vlqhex_to_string(n: i32, string: &mut String) {
3735 }
3836}
3937
40// Used during bitmap encoding
41enum Container {
42 /// number of ones, bits
43 Bits(Box<[u64; 1024]>),
44 /// list of entries
45 Array(Vec<u16>),
46 /// list of (start, len-1)
47 Run(Vec<(u16, u16)>),
48}
49impl Container {
50 fn popcount(&self) -> u32 {
51 match self {
52 Container::Bits(bits) => bits.iter().copied().map(|x| x.count_ones()).sum(),
53 Container::Array(array) => {
54 array.len().try_into().expect("array can't be bigger than 2**32")
55 }
56 Container::Run(runs) => {
57 runs.iter().copied().map(|(_, lenm1)| u32::from(lenm1) + 1).sum()
58 }
38pub fn read_signed_vlqhex_from_string(string: &[u8]) -> Option<(i32, usize)> {
39 let mut n = 0i32;
40 let mut i = 0;
41 while let Some(&c) = string.get(i) {
42 i += 1;
43 n = (n << 4) | i32::from(c & 0xF);
44 if c >= 96 {
45 // zig-zag encoding
46 let (sign, magnitude) = (n & 1, n >> 1);
47 let value = if sign == 0 { 1 } else { -1 } * magnitude;
48 return Some((value, i));
5949 }
6050 }
61 fn push(&mut self, value: u16) {
62 match self {
63 Container::Bits(bits) => bits[value as usize >> 6] |= 1 << (value & 0x3F),
64 Container::Array(array) => {
65 array.push(value);
66 if array.len() >= 4096 {
67 let array = std::mem::take(array);
68 *self = Container::Bits(Box::new([0; 1024]));
69 for value in array {
70 self.push(value);
71 }
72 }
73 }
74 Container::Run(runs) => {
75 if let Some(r) = runs.last_mut()
76 && r.0 + r.1 + 1 == value
77 {
78 r.1 += 1;
79 } else {
80 runs.push((value, 0));
81 }
82 }
51 None
52}
53
54pub fn write_postings_to_string(postings: &[Vec<u32>], buf: &mut Vec<u8>) {
55 for list in postings {
56 if list.is_empty() {
57 buf.push(0);
58 continue;
8359 }
84 }
85 fn try_make_run(&mut self) -> bool {
86 match self {
87 Container::Bits(bits) => {
88 let mut r: u64 = 0;
89 for (i, chunk) in bits.iter().copied().enumerate() {
90 let next_chunk =
91 i.checked_add(1).and_then(|i| bits.get(i)).copied().unwrap_or(0);
92 r += !chunk & u64::from((chunk << 1).count_ones());
93 r += !next_chunk & u64::from((chunk >> 63).count_ones());
94 }
95 if (2 + 4 * r) >= 8192 {
96 return false;
97 }
98 let bits = std::mem::replace(bits, Box::new([0; 1024]));
99 *self = Container::Run(Vec::new());
100 for (i, bits) in bits.iter().copied().enumerate() {
101 if bits == 0 {
102 continue;
103 }
104 for j in 0..64 {
105 let value = (u16::try_from(i).unwrap() << 6) | j;
106 if bits & (1 << j) != 0 {
107 self.push(value);
108 }
109 }
110 }
111 true
60 let len_before = buf.len();
61 stringdex::internals::encode::write_bitmap_to_bytes(&list, &mut *buf).unwrap();
62 let len_after = buf.len();
63 if len_after - len_before > 1 + (4 * list.len()) && list.len() < 0x3a {
64 buf.truncate(len_before);
65 buf.push(list.len() as u8);
66 for &item in list {
67 buf.push(item as u8);
68 buf.push((item >> 8) as u8);
69 buf.push((item >> 16) as u8);
70 buf.push((item >> 24) as u8);
11271 }
113 Container::Array(array) if array.len() <= 5 => false,
114 Container::Array(array) => {
115 let mut r = 0;
116 let mut prev = None;
117 for value in array.iter().copied() {
118 if value.checked_sub(1) != prev {
119 r += 1;
120 }
121 prev = Some(value);
122 }
123 if 2 + 4 * r >= 2 * array.len() + 2 {
124 return false;
125 }
126 let array = std::mem::take(array);
127 *self = Container::Run(Vec::new());
128 for value in array {
129 self.push(value);
130 }
131 true
132 }
133 Container::Run(_) => true,
13472 }
13573 }
13674}
13775
138// checked against roaring-rs in
139// https://gitlab.com/notriddle/roaring-test
140pub(crate) fn write_bitmap_to_bytes(
141 domain: &[u32],
142 mut out: impl std::io::Write,
143) -> std::io::Result<()> {
144 // https://arxiv.org/pdf/1603.06549.pdf
145 let mut keys = Vec::<u16>::new();
146 let mut containers = Vec::<Container>::new();
147 let mut key: u16;
148 let mut domain_iter = domain.iter().copied().peekable();
149 let mut has_run = false;
150 while let Some(entry) = domain_iter.next() {
151 key = (entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
152 let value: u16 = (entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit");
153 let mut container = Container::Array(vec![value]);
154 while let Some(entry) = domain_iter.peek().copied() {
155 let entry_key: u16 =
156 (entry >> 16).try_into().expect("shifted off the top 16 bits, so it should fit");
157 if entry_key != key {
158 break;
76pub fn read_postings_from_string(postings: &mut Vec<Vec<u32>>, mut buf: &[u8]) {
77 use stringdex::internals::decode::RoaringBitmap;
78 while let Some(&c) = buf.get(0) {
79 if c < 0x3a {
80 buf = &buf[1..];
81 let mut slot = Vec::new();
82 for _ in 0..c {
83 slot.push(
84 (buf[0] as u32)
85 | ((buf[1] as u32) << 8)
86 | ((buf[2] as u32) << 16)
87 | ((buf[3] as u32) << 24),
88 );
89 buf = &buf[4..];
15990 }
160 domain_iter.next().expect("peeking just succeeded");
161 container
162 .push((entry & 0x00_00_FF_FF).try_into().expect("AND 16 bits, so it should fit"));
163 }
164 keys.push(key);
165 has_run = container.try_make_run() || has_run;
166 containers.push(container);
167 }
168 // https://github.com/RoaringBitmap/RoaringFormatSpec
169 const SERIAL_COOKIE_NO_RUNCONTAINER: u32 = 12346;
170 const SERIAL_COOKIE: u32 = 12347;
171 const NO_OFFSET_THRESHOLD: u32 = 4;
172 let size: u32 = containers.len().try_into().unwrap();
173 let start_offset = if has_run {
174 out.write_all(&u32::to_le_bytes(SERIAL_COOKIE | ((size - 1) << 16)))?;
175 for set in containers.chunks(8) {
176 let mut b = 0;
177 for (i, container) in set.iter().enumerate() {
178 if matches!(container, &Container::Run(..)) {
179 b |= 1 << i;
180 }
181 }
182 out.write_all(&[b])?;
183 }
184 if size < NO_OFFSET_THRESHOLD {
185 4 + 4 * size + size.div_ceil(8)
91 postings.push(slot);
18692 } else {
187 4 + 8 * size + size.div_ceil(8)
188 }
189 } else {
190 out.write_all(&u32::to_le_bytes(SERIAL_COOKIE_NO_RUNCONTAINER))?;
191 out.write_all(&u32::to_le_bytes(containers.len().try_into().unwrap()))?;
192 4 + 4 + 4 * size + 4 * size
193 };
194 for (&key, container) in keys.iter().zip(&containers) {
195 // descriptive header
196 let key: u32 = key.into();
197 let count: u32 = container.popcount() - 1;
198 out.write_all(&u32::to_le_bytes((count << 16) | key))?;
199 }
200 if !has_run || size >= NO_OFFSET_THRESHOLD {
201 // offset header
202 let mut starting_offset = start_offset;
203 for container in &containers {
204 out.write_all(&u32::to_le_bytes(starting_offset))?;
205 starting_offset += match container {
206 Container::Bits(_) => 8192u32,
207 Container::Array(array) => u32::try_from(array.len()).unwrap() * 2,
208 Container::Run(runs) => 2 + u32::try_from(runs.len()).unwrap() * 4,
209 };
93 let (bitmap, consumed_bytes_len) =
94 RoaringBitmap::from_bytes(buf).unwrap_or_else(|| (RoaringBitmap::default(), 0));
95 assert_ne!(consumed_bytes_len, 0);
96 postings.push(bitmap.to_vec());
97 buf = &buf[consumed_bytes_len..];
21098 }
21199 }
212 for container in &containers {
213 match container {
214 Container::Bits(bits) => {
215 for chunk in bits.iter() {
216 out.write_all(&u64::to_le_bytes(*chunk))?;
217 }
218 }
219 Container::Array(array) => {
220 for value in array.iter() {
221 out.write_all(&u16::to_le_bytes(*value))?;
222 }
223 }
224 Container::Run(runs) => {
225 out.write_all(&u16::to_le_bytes(runs.len().try_into().unwrap()))?;
226 for (start, lenm1) in runs.iter().copied() {
227 out.write_all(&u16::to_le_bytes(start))?;
228 out.write_all(&u16::to_le_bytes(lenm1))?;
229 }
230 }
231 }
232 }
233 Ok(())
234}
235
236pub(crate) fn bitmap_to_string(domain: &[u32]) -> String {
237 let mut buf = Vec::new();
238 let mut strbuf = String::new();
239 write_bitmap_to_bytes(domain, &mut buf).unwrap();
240 BASE64_STANDARD.encode_string(&buf, &mut strbuf);
241 strbuf
242100}
src/librustdoc/html/render/write_shared.rs+14-64
......@@ -65,17 +65,17 @@ pub(crate) fn write_shared(
6565 // Write shared runs within a flock; disable thread dispatching of IO temporarily.
6666 let _lock = try_err!(flock::Lock::new(&lock_file, true, true, true), &lock_file);
6767
68 let SerializedSearchIndex { index, desc } = build_index(krate, &mut cx.shared.cache, tcx);
69 write_search_desc(cx, krate, &desc)?; // does not need to be merged
68 let search_index =
69 build_index(krate, &mut cx.shared.cache, tcx, &cx.dst, &cx.shared.resource_suffix)?;
7070
7171 let crate_name = krate.name(cx.tcx());
7272 let crate_name = crate_name.as_str(); // rand
7373 let crate_name_json = OrderedJson::serialize(crate_name).unwrap(); // "rand"
7474 let external_crates = hack_get_external_crate_names(&cx.dst, &cx.shared.resource_suffix)?;
7575 let info = CrateInfo {
76 version: CrateInfoVersion::V1,
76 version: CrateInfoVersion::V2,
7777 src_files_js: SourcesPart::get(cx, &crate_name_json)?,
78 search_index_js: SearchIndexPart::get(index, &cx.shared.resource_suffix)?,
78 search_index,
7979 all_crates: AllCratesPart::get(crate_name_json.clone(), &cx.shared.resource_suffix)?,
8080 crates_index: CratesIndexPart::get(crate_name, &external_crates)?,
8181 trait_impl: TraitAliasPart::get(cx, &crate_name_json)?,
......@@ -141,7 +141,7 @@ pub(crate) fn write_not_crate_specific(
141141 resource_suffix: &str,
142142 include_sources: bool,
143143) -> Result<(), Error> {
144 write_rendered_cross_crate_info(crates, dst, opt, include_sources)?;
144 write_rendered_cross_crate_info(crates, dst, opt, include_sources, resource_suffix)?;
145145 write_static_files(dst, opt, style_files, css_file_extension, resource_suffix)?;
146146 Ok(())
147147}
......@@ -151,13 +151,18 @@ fn write_rendered_cross_crate_info(
151151 dst: &Path,
152152 opt: &RenderOptions,
153153 include_sources: bool,
154 resource_suffix: &str,
154155) -> Result<(), Error> {
155156 let m = &opt.should_merge;
156157 if opt.should_emit_crate() {
157158 if include_sources {
158159 write_rendered_cci::<SourcesPart, _>(SourcesPart::blank, dst, crates, m)?;
159160 }
160 write_rendered_cci::<SearchIndexPart, _>(SearchIndexPart::blank, dst, crates, m)?;
161 crates
162 .iter()
163 .fold(SerializedSearchIndex::default(), |a, b| a.union(&b.search_index))
164 .sort()
165 .write_to(dst, resource_suffix)?;
161166 write_rendered_cci::<AllCratesPart, _>(AllCratesPart::blank, dst, crates, m)?;
162167 }
163168 write_rendered_cci::<TraitAliasPart, _>(TraitAliasPart::blank, dst, crates, m)?;
......@@ -215,38 +220,12 @@ fn write_static_files(
215220 Ok(())
216221}
217222
218/// Write the search description shards to disk
219fn write_search_desc(
220 cx: &mut Context<'_>,
221 krate: &Crate,
222 search_desc: &[(usize, String)],
223) -> Result<(), Error> {
224 let crate_name = krate.name(cx.tcx()).to_string();
225 let encoded_crate_name = OrderedJson::serialize(&crate_name).unwrap();
226 let path = PathBuf::from_iter([&cx.dst, Path::new("search.desc"), Path::new(&crate_name)]);
227 if path.exists() {
228 try_err!(fs::remove_dir_all(&path), &path);
229 }
230 for (i, (_, part)) in search_desc.iter().enumerate() {
231 let filename = static_files::suffix_path(
232 &format!("{crate_name}-desc-{i}-.js"),
233 &cx.shared.resource_suffix,
234 );
235 let path = path.join(filename);
236 let part = OrderedJson::serialize(part).unwrap();
237 let part = format!("searchState.loadedDescShard({encoded_crate_name}, {i}, {part})");
238 create_parents(&path)?;
239 try_err!(fs::write(&path, part), &path);
240 }
241 Ok(())
242}
243
244223/// Contains pre-rendered contents to insert into the CCI template
245224#[derive(Serialize, Deserialize, Clone, Debug)]
246225pub(crate) struct CrateInfo {
247226 version: CrateInfoVersion,
248227 src_files_js: PartsAndLocations<SourcesPart>,
249 search_index_js: PartsAndLocations<SearchIndexPart>,
228 search_index: SerializedSearchIndex,
250229 all_crates: PartsAndLocations<AllCratesPart>,
251230 crates_index: PartsAndLocations<CratesIndexPart>,
252231 trait_impl: PartsAndLocations<TraitAliasPart>,
......@@ -277,7 +256,7 @@ impl CrateInfo {
277256/// to provide better diagnostics about including an invalid file.
278257#[derive(Serialize, Deserialize, Clone, Debug)]
279258enum CrateInfoVersion {
280 V1,
259 V2,
281260}
282261
283262/// Paths (relative to the doc root) and their pre-merge contents
......@@ -331,36 +310,6 @@ trait CciPart: Sized + fmt::Display + DeserializeOwned + 'static {
331310 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self>;
332311}
333312
334#[derive(Serialize, Deserialize, Clone, Default, Debug)]
335struct SearchIndex;
336type SearchIndexPart = Part<SearchIndex, EscapedJson>;
337impl CciPart for SearchIndexPart {
338 type FileFormat = sorted_template::Js;
339 fn from_crate_info(crate_info: &CrateInfo) -> &PartsAndLocations<Self> {
340 &crate_info.search_index_js
341 }
342}
343
344impl SearchIndexPart {
345 fn blank() -> SortedTemplate<<Self as CciPart>::FileFormat> {
346 SortedTemplate::from_before_after(
347 r"var searchIndex = new Map(JSON.parse('[",
348 r"]'));
349if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
350else if (window.initSearch) window.initSearch(searchIndex);",
351 )
352 }
353
354 fn get(
355 search_index: OrderedJson,
356 resource_suffix: &str,
357 ) -> Result<PartsAndLocations<Self>, Error> {
358 let path = suffix_path("search-index.js", resource_suffix);
359 let search_index = EscapedJson::from(search_index);
360 Ok(PartsAndLocations::with(path, search_index))
361 }
362}
363
364313#[derive(Serialize, Deserialize, Clone, Default, Debug)]
365314struct AllCrates;
366315type AllCratesPart = Part<AllCrates, OrderedJson>;
......@@ -426,6 +375,7 @@ impl CratesIndexPart {
426375 fn blank(cx: &Context<'_>) -> SortedTemplate<<Self as CciPart>::FileFormat> {
427376 let page = layout::Page {
428377 title: "Index of crates",
378 short_title: "Crates",
429379 css_class: "mod sys",
430380 root_path: "./",
431381 static_root_path: cx.shared.static_root_path.as_deref(),
src/librustdoc/html/render/write_shared/tests.rs-33
......@@ -29,14 +29,6 @@ fn sources_template() {
2929 assert_eq!(but_last_line(&template.to_string()), r#"createSrcSidebar('["u","v"]');"#);
3030}
3131
32#[test]
33fn sources_parts() {
34 let parts =
35 SearchIndexPart::get(OrderedJson::serialize(["foo", "bar"]).unwrap(), "suffix").unwrap();
36 assert_eq!(&parts.parts[0].0, Path::new("search-indexsuffix.js"));
37 assert_eq!(&parts.parts[0].1.to_string(), r#"["foo","bar"]"#);
38}
39
4032#[test]
4133fn all_crates_template() {
4234 let mut template = AllCratesPart::blank();
......@@ -54,31 +46,6 @@ fn all_crates_parts() {
5446 assert_eq!(&parts.parts[0].1.to_string(), r#""crate""#);
5547}
5648
57#[test]
58fn search_index_template() {
59 let mut template = SearchIndexPart::blank();
60 assert_eq!(
61 but_last_line(&template.to_string()),
62 r"var searchIndex = new Map(JSON.parse('[]'));
63if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
64else if (window.initSearch) window.initSearch(searchIndex);"
65 );
66 template.append(EscapedJson::from(OrderedJson::serialize([1, 2]).unwrap()).to_string());
67 assert_eq!(
68 but_last_line(&template.to_string()),
69 r"var searchIndex = new Map(JSON.parse('[[1,2]]'));
70if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
71else if (window.initSearch) window.initSearch(searchIndex);"
72 );
73 template.append(EscapedJson::from(OrderedJson::serialize([4, 3]).unwrap()).to_string());
74 assert_eq!(
75 but_last_line(&template.to_string()),
76 r"var searchIndex = new Map(JSON.parse('[[1,2],[4,3]]'));
77if (typeof exports !== 'undefined') exports.searchIndex = searchIndex;
78else if (window.initSearch) window.initSearch(searchIndex);"
79 );
80}
81
8249#[test]
8350fn crates_index_part() {
8451 let external_crates = ["bar".to_string(), "baz".to_string()];
src/librustdoc/html/sources.rs+1
......@@ -230,6 +230,7 @@ impl SourceCollector<'_, '_> {
230230 );
231231 let page = layout::Page {
232232 title: &title,
233 short_title: &src_fname.to_string_lossy(),
233234 css_class: "src",
234235 root_path: &root_path,
235236 static_root_path: shared.static_root_path.as_deref(),
src/librustdoc/html/static/css/rustdoc.css+317-91
......@@ -258,6 +258,17 @@ h1, h2, h3, h4 {
258258 padding-bottom: 6px;
259259 margin-bottom: 15px;
260260}
261.search-results-main-heading {
262 grid-template-areas:
263 "main-heading-breadcrumbs main-heading-placeholder"
264 "main-heading-breadcrumbs main-heading-toolbar "
265 "main-heading-h1 main-heading-toolbar ";
266}
267.search-results-main-heading nav.sub {
268 grid-area: main-heading-h1;
269 align-items: end;
270 margin: 4px 0 8px 0;
271}
261272.rustdoc-breadcrumbs {
262273 grid-area: main-heading-breadcrumbs;
263274 line-height: 1.25;
......@@ -265,6 +276,16 @@ h1, h2, h3, h4 {
265276 position: relative;
266277 z-index: 1;
267278}
279.search-switcher {
280 grid-area: main-heading-breadcrumbs;
281 line-height: 1.5;
282 display: flex;
283 color: var(--main-color);
284 align-items: baseline;
285 white-space: nowrap;
286 padding-top: 8px;
287 min-height: 34px;
288}
268289.rustdoc-breadcrumbs a {
269290 padding: 5px 0 7px;
270291}
......@@ -305,7 +326,7 @@ h4.code-header {
305326#crate-search,
306327h1, h2, h3, h4, h5, h6,
307328.sidebar,
308.mobile-topbar,
329rustdoc-topbar,
309330.search-input,
310331.search-results .result-name,
311332.item-table dt > a,
......@@ -317,6 +338,7 @@ rustdoc-toolbar,
317338summary.hideme,
318339.scraped-example-list,
319340.rustdoc-breadcrumbs,
341.search-switcher,
320342/* This selector is for the items listed in the "all items" page. */
321343ul.all-items {
322344 font-family: "Fira Sans", Arial, NanumBarunGothic, sans-serif;
......@@ -329,7 +351,7 @@ a.anchor,
329351.rust a,
330352.sidebar h2 a,
331353.sidebar h3 a,
332.mobile-topbar h2 a,
354rustdoc-topbar h2 a,
333355h1 a,
334356.search-results a,
335357.search-results li,
......@@ -616,7 +638,7 @@ img {
616638 color: var(--sidebar-resizer-active);
617639}
618640
619.sidebar, .mobile-topbar, .sidebar-menu-toggle,
641.sidebar, rustdoc-topbar, .sidebar-menu-toggle,
620642#src-sidebar {
621643 background-color: var(--sidebar-background-color);
622644}
......@@ -857,7 +879,7 @@ ul.block, .block li, .block ul {
857879 margin-bottom: 1rem;
858880}
859881
860.mobile-topbar {
882rustdoc-topbar {
861883 display: none;
862884}
863885
......@@ -1098,16 +1120,15 @@ div.where {
10981120nav.sub {
10991121 flex-grow: 1;
11001122 flex-flow: row nowrap;
1101 margin: 4px 0 0 0;
11021123 display: flex;
1103 align-items: center;
1124 align-items: start;
1125 margin-top: 4px;
11041126}
11051127.search-form {
11061128 position: relative;
11071129 display: flex;
11081130 height: 34px;
11091131 flex-grow: 1;
1110 margin-bottom: 4px;
11111132}
11121133.src nav.sub {
11131134 margin: 0 0 -10px 0;
......@@ -1208,27 +1229,14 @@ table,
12081229 margin-left: 0;
12091230}
12101231
1211.search-results-title {
1212 margin-top: 0;
1213 white-space: nowrap;
1214 /* flex layout allows shrinking the <select> appropriately if it becomes too large */
1215 display: flex;
1216 /* make things look like in a line, despite the fact that we're using a layout
1217 with boxes (i.e. from the flex layout) */
1218 align-items: baseline;
1219}
1220.search-results-title + .sub-heading {
1221 color: var(--main-color);
1222 display: flex;
1223 align-items: baseline;
1224 white-space: nowrap;
1225}
12261232#crate-search-div {
12271233 /* ensures that 100% in properties of #crate-search-div:after
12281234 are relative to the size of this div */
12291235 position: relative;
12301236 /* allows this div (and with it the <select>-element "#crate-search") to be shrunk */
12311237 min-width: 0;
1238 /* keep label text for switcher from moving down when this appears */
1239 margin-top: -1px;
12321240}
12331241#crate-search {
12341242 padding: 0 23px 0 4px;
......@@ -1294,6 +1302,7 @@ so that we can apply CSS-filters to change the arrow color in themes */
12941302 flex-grow: 1;
12951303 background-color: var(--button-background-color);
12961304 color: var(--search-color);
1305 max-width: 100%;
12971306}
12981307.search-input:focus {
12991308 border-color: var(--search-input-focused-border-color);
......@@ -1459,14 +1468,14 @@ so that we can apply CSS-filters to change the arrow color in themes */
14591468}
14601469
14611470#settings.popover {
1462 --popover-arrow-offset: 202px;
1471 --popover-arrow-offset: 196px;
14631472 top: calc(100% - 16px);
14641473}
14651474
14661475/* use larger max-width for help popover, but not for help.html */
14671476#help.popover {
14681477 max-width: 600px;
1469 --popover-arrow-offset: 118px;
1478 --popover-arrow-offset: 115px;
14701479 top: calc(100% - 16px);
14711480}
14721481
......@@ -1929,10 +1938,12 @@ a.tooltip:hover::after {
19291938 color: inherit;
19301939}
19311940#search-tabs button:not(.selected) {
1941 --search-tab-button-background: var(--search-tab-button-not-selected-background);
19321942 background-color: var(--search-tab-button-not-selected-background);
19331943 border-top-color: var(--search-tab-button-not-selected-border-top-color);
19341944}
19351945#search-tabs button:hover, #search-tabs button.selected {
1946 --search-tab-button-background: var(--search-tab-button-selected-background);
19361947 background-color: var(--search-tab-button-selected-background);
19371948 border-top-color: var(--search-tab-button-selected-border-top-color);
19381949}
......@@ -1941,6 +1952,73 @@ a.tooltip:hover::after {
19411952 font-size: 1rem;
19421953 font-variant-numeric: tabular-nums;
19431954 color: var(--search-tab-title-count-color);
1955 position: relative;
1956}
1957
1958#search-tabs .count.loading {
1959 color: transparent;
1960}
1961
1962.search-form.loading {
1963 --search-tab-button-background: var(--button-background-color);
1964}
1965
1966#search-tabs .count.loading::before,
1967.search-form.loading::before
1968{
1969 width: 16px;
1970 height: 16px;
1971 border-radius: 16px;
1972 background: radial-gradient(
1973 var(--search-tab-button-background) 0 50%,
1974 transparent 50% 100%
1975 ), conic-gradient(
1976 var(--code-highlight-kw-color) 0deg 30deg,
1977 var(--code-highlight-prelude-color) 30deg 60deg,
1978 var(--code-highlight-number-color) 90deg 120deg,
1979 var(--code-highlight-lifetime-color ) 120deg 150deg,
1980 var(--code-highlight-comment-color) 150deg 180deg,
1981 var(--code-highlight-self-color) 180deg 210deg,
1982 var(--code-highlight-attribute-color) 210deg 240deg,
1983 var(--code-highlight-literal-color) 210deg 240deg,
1984 var(--code-highlight-macro-color) 240deg 270deg,
1985 var(--code-highlight-question-mark-color) 270deg 300deg,
1986 var(--code-highlight-prelude-val-color) 300deg 330deg,
1987 var(--code-highlight-doc-comment-color) 330deg 360deg
1988 );
1989 content: "";
1990 position: absolute;
1991 left: 2px;
1992 top: 2px;
1993 animation: rotating 1.25s linear infinite;
1994}
1995#search-tabs .count.loading::after,
1996.search-form.loading::after
1997{
1998 width: 18px;
1999 height: 18px;
2000 border-radius: 18px;
2001 background: conic-gradient(
2002 var(--search-tab-button-background) 0deg 180deg,
2003 transparent 270deg 360deg
2004 );
2005 content: "";
2006 position: absolute;
2007 left: 1px;
2008 top: 1px;
2009 animation: rotating 0.66s linear infinite;
2010}
2011
2012.search-form.loading::before {
2013 left: auto;
2014 right: 9px;
2015 top: 8px;
2016}
2017
2018.search-form.loading::after {
2019 left: auto;
2020 right: 8px;
2021 top: 8px;
19442022}
19452023
19462024#search .error code {
......@@ -1974,7 +2052,7 @@ a.tooltip:hover::after {
19742052 border-bottom: 1px solid var(--border-color);
19752053}
19762054
1977#settings-menu, #help-button, button#toggle-all-docs {
2055#search-button, .settings-menu, .help-menu, button#toggle-all-docs {
19782056 margin-left: var(--button-left-margin);
19792057 display: flex;
19802058 line-height: 1.25;
......@@ -1989,69 +2067,100 @@ a.tooltip:hover::after {
19892067 display: flex;
19902068 margin-right: 4px;
19912069 position: fixed;
2070 margin-top: 25px;
2071 left: 6px;
19922072 height: 34px;
19932073 width: 34px;
2074 z-index: calc(var(--desktop-sidebar-z-index) + 1);
19942075}
19952076.hide-sidebar #sidebar-button {
19962077 left: 6px;
19972078 background-color: var(--main-background-color);
1998 z-index: 1;
19992079}
20002080.src #sidebar-button {
2081 margin-top: 0;
2082 top: 8px;
20012083 left: 8px;
2002 z-index: calc(var(--desktop-sidebar-z-index) + 1);
2084 border-color: var(--border-color);
20032085}
20042086.hide-sidebar .src #sidebar-button {
20052087 position: static;
20062088}
2007#settings-menu > a, #help-button > a, #sidebar-button > a, button#toggle-all-docs {
2089#search-button > a,
2090.settings-menu > a,
2091.help-menu > a,
2092#sidebar-button > a,
2093button#toggle-all-docs {
20082094 display: flex;
20092095 align-items: center;
20102096 justify-content: center;
20112097 flex-direction: column;
20122098}
2013#settings-menu > a, #help-button > a, button#toggle-all-docs {
2099#search-button > a,
2100.settings-menu > a,
2101.help-menu > a,
2102button#toggle-all-docs {
20142103 border: 1px solid transparent;
20152104 border-radius: var(--button-border-radius);
20162105 color: var(--main-color);
20172106}
2018#settings-menu > a, #help-button > a, button#toggle-all-docs {
2107#search-button > a, .settings-menu > a, .help-menu > a, button#toggle-all-docs {
20192108 width: 80px;
20202109 border-radius: var(--toolbar-button-border-radius);
20212110}
2022#settings-menu > a, #help-button > a {
2111#search-button > a, .settings-menu > a, .help-menu > a {
20232112 min-width: 0;
20242113}
20252114#sidebar-button > a {
2026 background-color: var(--sidebar-background-color);
2115 border: solid 1px transparent;
2116 border-radius: var(--button-border-radius);
2117 background-color: var(--button-background-color);
20272118 width: 33px;
20282119}
2029#sidebar-button > a:hover, #sidebar-button > a:focus-visible {
2030 background-color: var(--main-background-color);
2120.src #sidebar-button > a {
2121 background-color: var(--sidebar-background-color);
2122 border-color: var(--border-color);
20312123}
20322124
2033#settings-menu > a:hover, #settings-menu > a:focus-visible,
2034#help-button > a:hover, #help-button > a:focus-visible,
2125#search-button > a:hover, #search-button > a:focus-visible,
2126.settings-menu > a:hover, .settings-menu > a:focus-visible,
2127.help-menu > a:hover, #help-menu > a:focus-visible,
2128#sidebar-button > a:hover, #sidebar-button > a:focus-visible,
2129#copy-path:hover, #copy-path:focus-visible,
20352130button#toggle-all-docs:hover, button#toggle-all-docs:focus-visible {
20362131 border-color: var(--settings-button-border-focus);
20372132 text-decoration: none;
20382133}
20392134
2040#settings-menu > a::before {
2135#search-button > a::before {
2136 /* Magnifying glass */
2137 content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
2138 width="18" height="18" viewBox="0 0 16 16">\
2139 <circle r="5" cy="7" cx="7" style="fill:none;stroke:black;stroke-width:3"/><path \
2140 d="M14.5,14.5 12,12" style="fill:none;stroke:black;stroke-width:3;stroke-linecap:round">\
2141 </path><desc>Search</desc>\
2142 </svg>');
2143 width: 18px;
2144 height: 18px;
2145 filter: var(--settings-menu-filter);
2146}
2147
2148.settings-menu > a::before {
20412149 /* Wheel <https://www.svgrepo.com/svg/384069/settings-cog-gear> */
20422150 content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
20432151 enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
2044 <path d="M10.25,6c0-0.1243286-0.0261841-0.241333-0.0366211-0.362915l1.6077881-1.5545654l\
2045 -1.25-2.1650391 c0,0-1.2674561,0.3625488-2.1323853,0.6099854c-0.2034912-0.1431885-0.421875\
2046 -0.2639771-0.6494751-0.3701782L7.25,0h-2.5 c0,0-0.3214111,1.2857666-0.5393066,2.1572876\
2047 C3.9830933,2.2634888,3.7647095,2.3842773,3.5612183,2.5274658L1.428833,1.9174805 \
2048 l-1.25,2.1650391c0,0,0.9641113,0.9321899,1.6077881,1.5545654C1.7761841,5.758667,\
2049 1.75,5.8756714,1.75,6 s0.0261841,0.241333,0.0366211,0.362915L0.178833,7.9174805l1.25,\
2050 2.1650391l2.1323853-0.6099854 c0.2034912,0.1432495,0.421875,0.2639771,0.6494751,0.3701782\
2051 L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.6494751\
2052 -0.3701782l2.1323853,0.6099854l1.25-2.1650391L10.2133789,6.362915 C10.2238159,6.241333,\
2053 10.25,6.1243286,10.25,6z M6,7.5C5.1715698,7.5,4.5,6.8284302,4.5,6S5.1715698,4.5,6,4.5S7.5\
2054 ,5.1715698,7.5,6 S6.8284302,7.5,6,7.5z" fill="black"/></svg>');
2152 <path d="m4.75 0s-0.32117 1.286-0.53906 2.1576c-0.2276 0.1062-0.44625 \
2153 0.2266-0.64974 0.36979l-2.1328-0.60938-1.25 2.1641s0.9644 0.93231 1.6081 1.5547c-0.010437 \
2154 0.12158-0.036458 0.23895-0.036458 0.36328s0.026021 0.2417 0.036458 0.36328l-1.6081 \
2155 1.5547 1.25 2.1641 2.1328-0.60937c0.20349 0.14325 0.42214 0.26359 0.64974 0.36979l0.53906 \
2156 2.1576h2.5l0.53906-2.1576c0.2276-0.1062 0.44625-0.22654 0.64974-0.36979l2.1328 0.60937 \
2157 1.25-2.1641-1.6081-1.5547c0.010437-0.12158 0.036458-0.23895 \
2158 0.036458-0.36328s-0.02602-0.2417-0.03646-0.36328l1.6081-1.5547-1.25-2.1641s-1.2679 \
2159 0.36194-2.1328 0.60938c-0.20349-0.14319-0.42214-0.26359-0.64974-0.36979l-0.53906-2.1576\
2160 zm1.25 2.5495c1.9058-2.877e-4 3.4508 1.5447 3.4505 3.4505 2.877e-4 1.9058-1.5447 3.4508-3.4505 \
2161 3.4505-1.9058 2.877e-4 -3.4508-1.5447-3.4505-3.4505-2.877e-4 -1.9058 1.5447-3.4508 \
2162 3.4505-3.4505z" fill="black"/>\
2163 <circle cx="6" cy="6" r="1.75" fill="none" stroke="black" stroke-width="1"/></svg>');
20552164 width: 18px;
20562165 height: 18px;
20572166 filter: var(--settings-menu-filter);
......@@ -2067,36 +2176,51 @@ button#toggle-all-docs::before {
20672176 filter: var(--settings-menu-filter);
20682177}
20692178
2070button#toggle-all-docs.will-expand::before {
2071 /* Custom arrow icon */
2072 content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
2073 enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg">\
2074 <path d="M2,5l4,-4l4,4M2,7l4,4l4,-4" stroke="black" fill="none" stroke-width="2px"/></svg>');
2075}
2076
2077#help-button > a::before {
2078 /* Question mark with circle */
2079 content: url('data:image/svg+xml,<svg width="18" height="18" viewBox="0 0 12 12" \
2080 enable-background="new 0 0 12 12" xmlns="http://www.w3.org/2000/svg" fill="none">\
2081 <circle r="5.25" cx="6" cy="6" stroke-width="1.25" stroke="black"/>\
2082 <text x="6" y="7" style="font:8px sans-serif;font-weight:1000" text-anchor="middle" \
2083 dominant-baseline="middle" fill="black">?</text></svg>');
2179.help-menu > a::before {
2180 /* Question mark with "circle" */
2181 content: url('data:image/svg+xml,\
2182 <svg width="18" height="18" enable-background="new 0 0 12 12" fill="none" \
2183 version="1.1" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"> \
2184 <path d="m6.007 0.6931c2.515 0 5.074 1.908 5.074 5.335 0 3.55-2.567 5.278-5.088 \
2185 5.278-2.477 0-5.001-1.742-5.001-5.3 0-3.38 2.527-5.314 5.014-5.314z" stroke="black" \
2186 stroke-width="1.5"/>\
2187 <path d="m5.999 7.932c0.3111 0 0.7062 0.2915 0.7062 0.7257 0 0.5458-0.3951 \
2188 0.8099-0.7081 0.8099-0.2973 0-0.7023-0.266-0.7023-0.7668 0-0.4695 0.3834-0.7688 \
2189 0.7042-0.7688z" fill="black"/>\
2190 <path d="m4.281 3.946c0.0312-0.03057 0.06298-0.06029 0.09528-0.08916 0.4833-0.432 1.084-0.6722 \
2191 1.634-0.6722 1.141 0 1.508 1.043 1.221 1.621-0.2753 0.5542-1.061 0.5065-1.273 \
2192 1.595-0.05728 0.2939 0.0134 0.9812 0.0134 1.205" fill="none" stroke="black" \
2193 stroke-width="1.25"/>\
2194 </svg>');
20842195 width: 18px;
20852196 height: 18px;
20862197 filter: var(--settings-menu-filter);
20872198}
20882199
2200/* design hack to cope with "Help" being far shorter than "Settings" etc */
2201.help-menu > a {
2202 width: 74px;
2203}
2204.help-menu > a > .label {
2205 padding-right: 1px;
2206}
2207#toggle-all-docs:not(.will-expand) > .label {
2208 padding-left: 1px;
2209}
2210
2211#search-button > a::before,
20892212button#toggle-all-docs::before,
2090#help-button > a::before,
2091#settings-menu > a::before {
2213.help-menu > a::before,
2214.settings-menu > a::before {
20922215 filter: var(--settings-menu-filter);
20932216 margin: 8px;
20942217}
20952218
20962219@media not (pointer: coarse) {
2220 #search-button > a:hover::before,
20972221 button#toggle-all-docs:hover::before,
2098 #help-button > a:hover::before,
2099 #settings-menu > a:hover::before {
2222 .help-menu > a:hover::before,
2223 .settings-menu > a:hover::before {
21002224 filter: var(--settings-menu-hover-filter);
21012225 }
21022226}
......@@ -2122,9 +2246,9 @@ rustdoc-toolbar span.label {
21222246 /* sidebar resizer image */
21232247 content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 22" \
21242248 fill="none" stroke="black">\
2125 <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
2126 <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
2127 <path d="m7.6121 3v16 M5.375 7.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
2249 <rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
2250 <circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
2251 <path d="m7.6121 4v14 M5.375 8.625h-2 m2 3h-2 m2 3h-2" stroke-width="1.25"/></svg>');
21282252 width: 22px;
21292253 height: 22px;
21302254}
......@@ -2137,7 +2261,8 @@ rustdoc-toolbar span.label {
21372261 margin-left: 10px;
21382262 padding: 0;
21392263 padding-left: 2px;
2140 border: 0;
2264 border: solid 1px transparent;
2265 border-radius: var(--button-border-radius);
21412266 font-size: 0;
21422267}
21432268#copy-path::before {
......@@ -2159,7 +2284,7 @@ rustdoc-toolbar span.label {
21592284 transform: rotate(360deg);
21602285 }
21612286}
2162#settings-menu.rotate > a img {
2287.settings-menu.rotate > a img {
21632288 animation: rotating 2s linear infinite;
21642289}
21652290
......@@ -2402,6 +2527,9 @@ However, it's not needed with smaller screen width because the doc/code block is
24022527 opacity: 0.75;
24032528 filter: var(--mobile-sidebar-menu-filter);
24042529}
2530.src #sidebar-button > a:hover {
2531 background: var(--main-background-color);
2532}
24052533.sidebar-menu-toggle:hover::before,
24062534.sidebar-menu-toggle:active::before,
24072535.sidebar-menu-toggle:focus::before {
......@@ -2410,8 +2538,8 @@ However, it's not needed with smaller screen width because the doc/code block is
24102538
24112539/* Media Queries */
24122540
2413/* Make sure all the buttons line wrap at the same time */
24142541@media (max-width: 850px) {
2542 /* Make sure all the buttons line wrap at the same time */
24152543 #search-tabs .count {
24162544 display: block;
24172545 }
......@@ -2421,6 +2549,81 @@ However, it's not needed with smaller screen width because the doc/code block is
24212549 .side-by-side > div {
24222550 width: auto;
24232551 }
2552
2553 /* Text label takes up too much space at this size. */
2554 .main-heading {
2555 grid-template-areas:
2556 "main-heading-breadcrumbs main-heading-toolbar"
2557 "main-heading-h1 main-heading-toolbar"
2558 "main-heading-sub-heading main-heading-toolbar";
2559 }
2560 .search-results-main-heading {
2561 display: grid;
2562 grid-template-areas:
2563 "main-heading-breadcrumbs main-heading-toolbar"
2564 "main-heading-breadcrumbs main-heading-toolbar"
2565 "main-heading-h1 main-heading-toolbar";
2566 }
2567 rustdoc-toolbar {
2568 margin-top: -10px;
2569 display: grid;
2570 grid-template-areas:
2571 "x settings help"
2572 "search summary summary";
2573 grid-template-rows: 35px 1fr;
2574 }
2575 .search-results-main-heading rustdoc-toolbar {
2576 display: grid;
2577 grid-template-areas:
2578 "settings help"
2579 "search search";
2580 }
2581 .search-results-main-heading #toggle-all-docs {
2582 display: none;
2583 }
2584 rustdoc-toolbar .settings-menu span.label,
2585 rustdoc-toolbar .help-menu span.label
2586 {
2587 display: none;
2588 }
2589 rustdoc-toolbar .settings-menu {
2590 grid-area: settings;
2591 }
2592 rustdoc-toolbar .help-menu {
2593 grid-area: help;
2594 }
2595 rustdoc-toolbar .settings-menu {
2596 grid-area: settings;
2597 }
2598 rustdoc-toolbar #search-button {
2599 grid-area: search;
2600 }
2601 rustdoc-toolbar #toggle-all-docs {
2602 grid-area: summary;
2603 }
2604 rustdoc-toolbar .settings-menu,
2605 rustdoc-toolbar .help-menu {
2606 height: 35px;
2607 }
2608 rustdoc-toolbar .settings-menu > a,
2609 rustdoc-toolbar .help-menu > a {
2610 border-radius: 2px;
2611 text-align: center;
2612 width: 34px;
2613 padding: 5px 0;
2614 }
2615 rustdoc-toolbar .settings-menu > a:before,
2616 rustdoc-toolbar .help-menu > a:before {
2617 margin: 0 4px;
2618 }
2619 #settings.popover {
2620 top: 16px;
2621 --popover-arrow-offset: 58px;
2622 }
2623 #help.popover {
2624 top: 16px;
2625 --popover-arrow-offset: 16px;
2626 }
24242627}
24252628
24262629/*
......@@ -2435,7 +2638,7 @@ in src-script.js and main.js
24352638
24362639 /* When linking to an item with an `id` (for instance, by clicking a link in the sidebar,
24372640 or visiting a URL with a fragment like `#method.new`, we don't want the item to be obscured
2438 by the topbar. Anything with an `id` gets scroll-margin-top equal to .mobile-topbar's size.
2641 by the topbar. Anything with an `id` gets scroll-margin-top equal to rustdoc-topbar's size.
24392642 */
24402643 *[id] {
24412644 scroll-margin-top: 45px;
......@@ -2451,18 +2654,32 @@ in src-script.js and main.js
24512654 visibility: hidden;
24522655 }
24532656
2454 /* Text label takes up too much space at this size. */
2455 rustdoc-toolbar span.label {
2657
2658 /* Pull settings and help up into the top bar. */
2659 rustdoc-topbar span.label,
2660 html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .settings-menu > a,
2661 html:not(.hide-sidebar) .rustdoc:not(.src) rustdoc-toolbar .help-menu > a
2662 {
24562663 display: none;
24572664 }
2458 #settings-menu > a, #help-button > a, button#toggle-all-docs {
2665 rustdoc-topbar .settings-menu > a,
2666 rustdoc-topbar .help-menu > a {
24592667 width: 33px;
2668 line-height: 0;
2669 }
2670 rustdoc-topbar .settings-menu > a:hover,
2671 rustdoc-topbar .help-menu > a:hover {
2672 border: none;
2673 background: var(--main-background-color);
2674 border-radius: 0;
24602675 }
24612676 #settings.popover {
2462 --popover-arrow-offset: 86px;
2677 top: 32px;
2678 --popover-arrow-offset: 48px;
24632679 }
24642680 #help.popover {
2465 --popover-arrow-offset: 48px;
2681 top: 32px;
2682 --popover-arrow-offset: 12px;
24662683 }
24672684
24682685 .rustdoc {
......@@ -2471,13 +2688,13 @@ in src-script.js and main.js
24712688 display: block;
24722689 }
24732690
2474 main {
2691 html:not(.hide-sidebar) main {
24752692 padding-left: 15px;
24762693 padding-top: 0px;
24772694 }
24782695
24792696 /* Hide the logo and item name from the sidebar. Those are displayed
2480 in the mobile-topbar instead. */
2697 in the rustdoc-topbar instead. */
24812698 .sidebar .logo-container,
24822699 .sidebar .location,
24832700 .sidebar-resizer {
......@@ -2510,6 +2727,9 @@ in src-script.js and main.js
25102727 height: 100vh;
25112728 border: 0;
25122729 }
2730 html .src main {
2731 padding: 18px 0;
2732 }
25132733 .src .search-form {
25142734 margin-left: 40px;
25152735 }
......@@ -2529,9 +2749,9 @@ in src-script.js and main.js
25292749 left: 0;
25302750 }
25312751
2532 .mobile-topbar h2 {
2752 rustdoc-topbar > h2 {
25332753 padding-bottom: 0;
2534 margin: auto 0.5em auto auto;
2754 margin: auto;
25352755 overflow: hidden;
25362756 /* Rare exception to specifying font sizes in rem. Since the topbar
25372757 height is specified in pixels, this also has to be specified in
......@@ -2540,32 +2760,34 @@ in src-script.js and main.js
25402760 font-size: 24px;
25412761 white-space: nowrap;
25422762 text-overflow: ellipsis;
2763 text-align: center;
25432764 }
25442765
2545 .mobile-topbar .logo-container > img {
2766 rustdoc-topbar .logo-container > img {
25462767 max-width: 35px;
25472768 max-height: 35px;
25482769 margin: 5px 0 5px 20px;
25492770 }
25502771
2551 .mobile-topbar {
2772 rustdoc-topbar {
25522773 display: flex;
25532774 flex-direction: row;
25542775 position: sticky;
25552776 z-index: 10;
2556 font-size: 2rem;
25572777 height: 45px;
25582778 width: 100%;
25592779 left: 0;
25602780 top: 0;
25612781 }
25622782
2563 .hide-sidebar .mobile-topbar {
2783 .hide-sidebar rustdoc-topbar {
25642784 display: none;
25652785 }
25662786
25672787 .sidebar-menu-toggle {
2568 width: 45px;
2788 /* prevent flexbox shrinking */
2789 width: 41px;
2790 min-width: 41px;
25692791 border: none;
25702792 line-height: 0;
25712793 }
......@@ -2591,9 +2813,13 @@ in src-script.js and main.js
25912813 #sidebar-button > a::before {
25922814 content: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" \
25932815 viewBox="0 0 22 22" fill="none" stroke="black">\
2594 <rect x="1" y="1" width="20" height="20" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
2595 <circle cx="4.375" cy="4.375" r="1" stroke-width=".75"/>\
2596 <path d="m3 7.375h16m0-3h-4" stroke-width="1.25"/></svg>');
2816 <rect x="1" y="2" width="20" height="18" ry="1.5" stroke-width="1.5" stroke="%23777"/>\
2817 <g fill="black" stroke="none">\
2818 <circle cx="4.375" cy="5.375" r="1" stroke-width=".75"/>\
2819 <circle cx="17.375" cy="5.375" r="1" stroke-width=".75"/>\
2820 <circle cx="14.375" cy="5.375" r="1" stroke-width=".75"/>\
2821 </g>\
2822 <path d="m3 8.375h16" stroke-width="1.25"/></svg>');
25972823 width: 22px;
25982824 height: 22px;
25992825 }
......@@ -3283,7 +3509,7 @@ Original by Dempfi (https://github.com/dempfi/ayu)
32833509 border-bottom: 1px solid rgba(242, 151, 24, 0.3);
32843510}
32853511
3286:root[data-theme="ayu"] #settings-menu > a img,
3512:root[data-theme="ayu"] .settings-menu > a img,
32873513:root[data-theme="ayu"] #sidebar-button > a::before {
32883514 filter: invert(100);
32893515}
src/librustdoc/html/static/js/main.js+244-141
......@@ -54,23 +54,6 @@ function showMain() {
5454window.rootPath = getVar("root-path");
5555window.currentCrate = getVar("current-crate");
5656
57function setMobileTopbar() {
58 // FIXME: It would be nicer to generate this text content directly in HTML,
59 // but with the current code it's hard to get the right information in the right place.
60 const mobileTopbar = document.querySelector(".mobile-topbar");
61 const locationTitle = document.querySelector(".sidebar h2.location");
62 if (mobileTopbar) {
63 const mobileTitle = document.createElement("h2");
64 mobileTitle.className = "location";
65 if (hasClass(document.querySelector(".rustdoc"), "crate")) {
66 mobileTitle.innerHTML = `Crate <a href="#">${window.currentCrate}</a>`;
67 } else if (locationTitle) {
68 mobileTitle.innerHTML = locationTitle.innerHTML;
69 }
70 mobileTopbar.appendChild(mobileTitle);
71 }
72}
73
7457/**
7558 * Gets the human-readable string for the virtual-key code of the
7659 * given KeyboardEvent, ev.
......@@ -84,6 +67,7 @@ function setMobileTopbar() {
8467 * So I guess you could say things are getting pretty interoperable.
8568 *
8669 * @param {KeyboardEvent} ev
70 * @returns {string}
8771 */
8872function getVirtualKey(ev) {
8973 if ("key" in ev && typeof ev.key !== "undefined") {
......@@ -98,18 +82,8 @@ function getVirtualKey(ev) {
9882}
9983
10084const MAIN_ID = "main-content";
101const SETTINGS_BUTTON_ID = "settings-menu";
10285const ALTERNATIVE_DISPLAY_ID = "alternative-display";
10386const NOT_DISPLAYED_ID = "not-displayed";
104const HELP_BUTTON_ID = "help-button";
105
106function getSettingsButton() {
107 return document.getElementById(SETTINGS_BUTTON_ID);
108}
109
110function getHelpButton() {
111 return document.getElementById(HELP_BUTTON_ID);
112}
11387
11488// Returns the current URL without any query parameter or hash.
11589function getNakedUrl() {
......@@ -174,7 +148,7 @@ function getNotDisplayedElem() {
174148 * contains the displayed element (there can be only one at the same time!). So basically, we switch
175149 * elements between the two `<section>` elements.
176150 *
177 * @param {HTMLElement|null} elemToDisplay
151 * @param {Element|null} elemToDisplay
178152 */
179153function switchDisplayedElement(elemToDisplay) {
180154 const el = getAlternativeDisplayElem();
......@@ -239,14 +213,14 @@ function preLoadCss(cssUrl) {
239213 document.head.append(script);
240214 }
241215
242 const settingsButton = getSettingsButton();
243 if (settingsButton) {
244 settingsButton.onclick = event => {
216 onEachLazy(document.querySelectorAll(".settings-menu"), settingsMenu => {
217 /** @param {MouseEvent} event */
218 settingsMenu.querySelector("a").onclick = event => {
245219 if (event.ctrlKey || event.altKey || event.metaKey) {
246220 return;
247221 }
248222 window.hideAllModals(false);
249 addClass(getSettingsButton(), "rotate");
223 addClass(settingsMenu, "rotate");
250224 event.preventDefault();
251225 // Sending request for the CSS and the JS files at the same time so it will
252226 // hopefully be loaded when the JS will generate the settings content.
......@@ -268,15 +242,42 @@ function preLoadCss(cssUrl) {
268242 }
269243 }, 0);
270244 };
271 }
245 });
272246
273247 window.searchState = {
274248 rustdocToolbar: document.querySelector("rustdoc-toolbar"),
275249 loadingText: "Loading search results...",
276 // This will always be an HTMLInputElement, but tsc can't see that
277 // @ts-expect-error
278 input: document.getElementsByClassName("search-input")[0],
279 outputElement: () => {
250 inputElement: () => {
251 let el = document.getElementsByClassName("search-input")[0];
252 if (!el) {
253 const out = nonnull(nonnull(window.searchState.outputElement()).parentElement);
254 const hdr = document.createElement("div");
255 hdr.className = "main-heading search-results-main-heading";
256 const params = window.searchState.getQueryStringParams();
257 const autofocusParam = params.search === "" ? "autofocus" : "";
258 hdr.innerHTML = `<nav class="sub">
259 <form class="search-form loading">
260 <span></span> <!-- This empty span is a hacky fix for Safari: see #93184 -->
261 <input
262 ${autofocusParam}
263 class="search-input"
264 name="search"
265 aria-label="Run search in the documentation"
266 autocomplete="off"
267 spellcheck="false"
268 placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"
269 type="search">
270 </form>
271 </nav><div class="search-switcher"></div>`;
272 out.insertBefore(hdr, window.searchState.outputElement());
273 el = document.getElementsByClassName("search-input")[0];
274 }
275 if (el instanceof HTMLInputElement) {
276 return el;
277 }
278 return null;
279 },
280 containerElement: () => {
280281 let el = document.getElementById("search");
281282 if (!el) {
282283 el = document.createElement("section");
......@@ -285,6 +286,19 @@ function preLoadCss(cssUrl) {
285286 }
286287 return el;
287288 },
289 outputElement: () => {
290 const container = window.searchState.containerElement();
291 if (!container) {
292 return null;
293 }
294 let el = container.querySelector(".search-out");
295 if (!el) {
296 el = document.createElement("div");
297 el.className = "search-out";
298 container.appendChild(el);
299 }
300 return el;
301 },
288302 title: document.title,
289303 titleBeforeSearch: document.title,
290304 timeout: null,
......@@ -303,25 +317,52 @@ function preLoadCss(cssUrl) {
303317 }
304318 },
305319 isDisplayed: () => {
306 const outputElement = window.searchState.outputElement();
307 return !!outputElement &&
308 !!outputElement.parentElement &&
309 outputElement.parentElement.id === ALTERNATIVE_DISPLAY_ID;
320 const container = window.searchState.containerElement();
321 if (!container) {
322 return false;
323 }
324 return !!container.parentElement && container.parentElement.id ===
325 ALTERNATIVE_DISPLAY_ID;
310326 },
311327 // Sets the focus on the search bar at the top of the page
312328 focus: () => {
313 window.searchState.input && window.searchState.input.focus();
329 const inputElement = window.searchState.inputElement();
330 window.searchState.showResults();
331 if (inputElement) {
332 inputElement.focus();
333 // Avoid glitch if something focuses the search button after clicking.
334 requestAnimationFrame(() => inputElement.focus());
335 }
314336 },
315337 // Removes the focus from the search bar.
316338 defocus: () => {
317 window.searchState.input && window.searchState.input.blur();
339 nonnull(window.searchState.inputElement()).blur();
318340 },
319 showResults: search => {
320 if (search === null || typeof search === "undefined") {
321 search = window.searchState.outputElement();
341 toggle: () => {
342 if (window.searchState.isDisplayed()) {
343 window.searchState.defocus();
344 window.searchState.hideResults();
345 } else {
346 window.searchState.focus();
322347 }
323 switchDisplayedElement(search);
348 },
349 showResults: () => {
324350 document.title = window.searchState.title;
351 if (window.searchState.isDisplayed()) {
352 return;
353 }
354 const search = window.searchState.containerElement();
355 switchDisplayedElement(search);
356 const btn = document.querySelector("#search-button a");
357 if (browserSupportsHistoryApi() && btn instanceof HTMLAnchorElement &&
358 window.searchState.getQueryStringParams().search === undefined
359 ) {
360 history.pushState(null, "", btn.href);
361 }
362 const btnLabel = document.querySelector("#search-button a span.label");
363 if (btnLabel) {
364 btnLabel.innerHTML = "Exit";
365 }
325366 },
326367 removeQueryParameters: () => {
327368 // We change the document title.
......@@ -334,6 +375,10 @@ function preLoadCss(cssUrl) {
334375 switchDisplayedElement(null);
335376 // We also remove the query parameter from the URL.
336377 window.searchState.removeQueryParameters();
378 const btnLabel = document.querySelector("#search-button a span.label");
379 if (btnLabel) {
380 btnLabel.innerHTML = "Search";
381 }
337382 },
338383 getQueryStringParams: () => {
339384 /** @type {Object.<any, string>} */
......@@ -348,11 +393,11 @@ function preLoadCss(cssUrl) {
348393 return params;
349394 },
350395 setup: () => {
351 const search_input = window.searchState.input;
396 let searchLoaded = false;
397 const search_input = window.searchState.inputElement();
352398 if (!search_input) {
353399 return;
354400 }
355 let searchLoaded = false;
356401 // If you're browsing the nightly docs, the page might need to be refreshed for the
357402 // search to work because the hash of the JS scripts might have changed.
358403 function sendSearchForm() {
......@@ -363,21 +408,102 @@ function preLoadCss(cssUrl) {
363408 if (!searchLoaded) {
364409 searchLoaded = true;
365410 // @ts-expect-error
366 loadScript(getVar("static-root-path") + getVar("search-js"), sendSearchForm);
367 loadScript(resourcePath("search-index", ".js"), sendSearchForm);
411 window.rr_ = data => {
412 // @ts-expect-error
413 window.searchIndex = data;
414 };
415 if (!window.StringdexOnload) {
416 window.StringdexOnload = [];
417 }
418 window.StringdexOnload.push(() => {
419 loadScript(
420 // @ts-expect-error
421 getVar("static-root-path") + getVar("search-js"),
422 sendSearchForm,
423 );
424 });
425 // @ts-expect-error
426 loadScript(getVar("static-root-path") + getVar("stringdex-js"), sendSearchForm);
427 loadScript(resourcePath("search.index/root", ".js"), sendSearchForm);
368428 }
369429 }
370430
371431 search_input.addEventListener("focus", () => {
372 window.searchState.origPlaceholder = search_input.placeholder;
373 search_input.placeholder = "Type your search here.";
374432 loadSearch();
375433 });
376434
377 if (search_input.value !== "") {
378 loadSearch();
435 const btn = document.getElementById("search-button");
436 if (btn) {
437 btn.onclick = event => {
438 if (event.ctrlKey || event.altKey || event.metaKey) {
439 return;
440 }
441 event.preventDefault();
442 window.searchState.toggle();
443 loadSearch();
444 };
445 }
446
447 // Push and pop states are used to add search results to the browser
448 // history.
449 if (browserSupportsHistoryApi()) {
450 // Store the previous <title> so we can revert back to it later.
451 const previousTitle = document.title;
452
453 window.addEventListener("popstate", e => {
454 const params = window.searchState.getQueryStringParams();
455 // Revert to the previous title manually since the History
456 // API ignores the title parameter.
457 document.title = previousTitle;
458 // Synchronize search bar with query string state and
459 // perform the search. This will empty the bar if there's
460 // nothing there, which lets you really go back to a
461 // previous state with nothing in the bar.
462 const inputElement = window.searchState.inputElement();
463 if (params.search !== undefined && inputElement !== null) {
464 loadSearch();
465 inputElement.value = params.search;
466 // Some browsers fire "onpopstate" for every page load
467 // (Chrome), while others fire the event only when actually
468 // popping a state (Firefox), which is why search() is
469 // called both here and at the end of the startSearch()
470 // function.
471 e.preventDefault();
472 window.searchState.showResults();
473 if (params.search === "") {
474 window.searchState.focus();
475 }
476 } else {
477 // When browsing back from search results the main page
478 // visibility must be reset.
479 window.searchState.hideResults();
480 }
481 });
379482 }
380483
484 // This is required in firefox to avoid this problem: Navigating to a search result
485 // with the keyboard, hitting enter, and then hitting back would take you back to
486 // the doc page, rather than the search that should overlay it.
487 // This was an interaction between the back-forward cache and our handlers
488 // that try to sync state between the URL and the search input. To work around it,
489 // do a small amount of re-init on page show.
490 window.onpageshow = () => {
491 const inputElement = window.searchState.inputElement();
492 const qSearch = window.searchState.getQueryStringParams().search;
493 if (qSearch !== undefined && inputElement !== null) {
494 if (inputElement.value === "") {
495 inputElement.value = qSearch;
496 }
497 window.searchState.showResults();
498 if (qSearch === "") {
499 loadSearch();
500 window.searchState.focus();
501 }
502 } else {
503 window.searchState.hideResults();
504 }
505 };
506
381507 const params = window.searchState.getQueryStringParams();
382508 if (params.search !== undefined) {
383509 window.searchState.setLoadingSearch();
......@@ -386,13 +512,9 @@ function preLoadCss(cssUrl) {
386512 },
387513 setLoadingSearch: () => {
388514 const search = window.searchState.outputElement();
389 if (!search) {
390 return;
391 }
392 search.innerHTML = "<h3 class=\"search-loading\">" +
393 window.searchState.loadingText +
394 "</h3>";
395 window.searchState.showResults(search);
515 nonnull(search).innerHTML = "<h3 class=\"search-loading\">" +
516 window.searchState.loadingText + "</h3>";
517 window.searchState.showResults();
396518 },
397519 descShards: new Map(),
398520 loadDesc: async function({descShard, descIndex}) {
......@@ -1500,15 +1622,13 @@ function preLoadCss(cssUrl) {
15001622
15011623 // @ts-expect-error
15021624 function helpBlurHandler(event) {
1503 // @ts-expect-error
1504 if (!getHelpButton().contains(document.activeElement) &&
1505 // @ts-expect-error
1506 !getHelpButton().contains(event.relatedTarget) &&
1507 // @ts-expect-error
1508 !getSettingsButton().contains(document.activeElement) &&
1509 // @ts-expect-error
1510 !getSettingsButton().contains(event.relatedTarget)
1511 ) {
1625 const isInPopover = onEachLazy(
1626 document.querySelectorAll(".settings-menu, .help-menu"),
1627 menu => {
1628 return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
1629 },
1630 );
1631 if (!isInPopover) {
15121632 window.hidePopoverMenus();
15131633 }
15141634 }
......@@ -1571,10 +1691,9 @@ function preLoadCss(cssUrl) {
15711691
15721692 const container = document.createElement("div");
15731693 if (!isHelpPage) {
1574 container.className = "popover";
1694 container.className = "popover content";
15751695 }
15761696 container.id = "help";
1577 container.style.display = "none";
15781697
15791698 const side_by_side = document.createElement("div");
15801699 side_by_side.className = "side-by-side";
......@@ -1590,17 +1709,16 @@ function preLoadCss(cssUrl) {
15901709 help_section.appendChild(container);
15911710 // @ts-expect-error
15921711 document.getElementById("main-content").appendChild(help_section);
1593 container.style.display = "block";
15941712 } else {
1595 const help_button = getHelpButton();
1596 // @ts-expect-error
1597 help_button.appendChild(container);
1598
1599 container.onblur = helpBlurHandler;
1600 // @ts-expect-error
1601 help_button.onblur = helpBlurHandler;
1602 // @ts-expect-error
1603 help_button.children[0].onblur = helpBlurHandler;
1713 onEachLazy(document.getElementsByClassName("help-menu"), menu => {
1714 if (menu.offsetWidth !== 0) {
1715 menu.appendChild(container);
1716 container.onblur = helpBlurHandler;
1717 menu.onblur = helpBlurHandler;
1718 menu.children[0].onblur = helpBlurHandler;
1719 return true;
1720 }
1721 });
16041722 }
16051723
16061724 return container;
......@@ -1621,80 +1739,57 @@ function preLoadCss(cssUrl) {
16211739 * Hide all the popover menus.
16221740 */
16231741 window.hidePopoverMenus = () => {
1624 onEachLazy(document.querySelectorAll("rustdoc-toolbar .popover"), elem => {
1742 onEachLazy(document.querySelectorAll(".settings-menu .popover"), elem => {
16251743 elem.style.display = "none";
16261744 });
1627 const button = getHelpButton();
1628 if (button) {
1629 removeClass(button, "help-open");
1630 }
1745 onEachLazy(document.querySelectorAll(".help-menu .popover"), elem => {
1746 elem.parentElement.removeChild(elem);
1747 });
16311748 };
16321749
1633 /**
1634 * Returns the help menu element (not the button).
1635 *
1636 * @param {boolean} buildNeeded - If this argument is `false`, the help menu element won't be
1637 * built if it doesn't exist.
1638 *
1639 * @return {HTMLElement}
1640 */
1641 function getHelpMenu(buildNeeded) {
1642 // @ts-expect-error
1643 let menu = getHelpButton().querySelector(".popover");
1644 if (!menu && buildNeeded) {
1645 menu = buildHelpMenu();
1646 }
1647 // @ts-expect-error
1648 return menu;
1649 }
1650
16511750 /**
16521751 * Show the help popup menu.
16531752 */
16541753 function showHelp() {
1754 window.hideAllModals(false);
16551755 // Prevent `blur` events from being dispatched as a result of closing
16561756 // other modals.
1657 const button = getHelpButton();
1658 addClass(button, "help-open");
1659 // @ts-expect-error
1660 button.querySelector("a").focus();
1661 const menu = getHelpMenu(true);
1662 if (menu.style.display === "none") {
1663 // @ts-expect-error
1664 window.hideAllModals();
1665 menu.style.display = "";
1666 }
1757 onEachLazy(document.querySelectorAll(".help-menu a"), menu => {
1758 if (menu.offsetWidth !== 0) {
1759 menu.focus();
1760 return true;
1761 }
1762 });
1763 buildHelpMenu();
16671764 }
16681765
1669 const helpLink = document.querySelector(`#${HELP_BUTTON_ID} > a`);
16701766 if (isHelpPage) {
16711767 buildHelpMenu();
1672 } else if (helpLink) {
1673 helpLink.addEventListener("click", event => {
1674 // By default, have help button open docs in a popover.
1675 // If user clicks with a moderator, though, use default browser behavior,
1676 // probably opening in a new window or tab.
1677 if (!helpLink.contains(helpLink) ||
1678 // @ts-expect-error
1679 event.ctrlKey ||
1680 // @ts-expect-error
1681 event.altKey ||
1682 // @ts-expect-error
1683 event.metaKey) {
1684 return;
1685 }
1686 event.preventDefault();
1687 const menu = getHelpMenu(true);
1688 const shouldShowHelp = menu.style.display === "none";
1689 if (shouldShowHelp) {
1690 showHelp();
1691 } else {
1692 window.hidePopoverMenus();
1693 }
1768 } else {
1769 onEachLazy(document.querySelectorAll(".help-menu > a"), helpLink => {
1770 helpLink.addEventListener(
1771 "click",
1772 /** @param {MouseEvent} event */
1773 event => {
1774 // By default, have help button open docs in a popover.
1775 // If user clicks with a moderator, though, use default browser behavior,
1776 // probably opening in a new window or tab.
1777 if (event.ctrlKey ||
1778 event.altKey ||
1779 event.metaKey) {
1780 return;
1781 }
1782 event.preventDefault();
1783 if (document.getElementById("help")) {
1784 window.hidePopoverMenus();
1785 } else {
1786 showHelp();
1787 }
1788 },
1789 );
16941790 });
16951791 }
16961792
1697 setMobileTopbar();
16981793 addSidebarItems();
16991794 addSidebarCrates();
17001795 onHashChange(null);
......@@ -1746,7 +1841,15 @@ function preLoadCss(cssUrl) {
17461841 // On larger, "desktop-sized" viewports (though that includes many
17471842 // tablets), it's fixed-position, appears in the left side margin,
17481843 // and it can be activated by resizing the sidebar into nothing.
1749 const sidebarButton = document.getElementById("sidebar-button");
1844 let sidebarButton = document.getElementById("sidebar-button");
1845 const body = document.querySelector(".main-heading");
1846 if (!sidebarButton && body) {
1847 sidebarButton = document.createElement("div");
1848 sidebarButton.id = "sidebar-button";
1849 const path = `${window.rootPath}${window.currentCrate}/all.html`;
1850 sidebarButton.innerHTML = `<a href="${path}" title="show sidebar"></a>`;
1851 body.insertBefore(sidebarButton, body.firstChild);
1852 }
17501853 if (sidebarButton) {
17511854 sidebarButton.addEventListener("click", e => {
17521855 removeClass(document.documentElement, "hide-sidebar");
src/librustdoc/html/static/js/rustdoc.d.ts+134-118
......@@ -2,6 +2,8 @@
22// not put into the JavaScript we include as part of the documentation. It is used for
33// type checking. See README.md in this directory for more info.
44
5import { RoaringBitmap } from "./stringdex";
6
57/* eslint-disable */
68declare global {
79 /** Search engine data used by main.js and search.js */
......@@ -10,6 +12,17 @@ declare global {
1012 declare function nonnull(x: T|null, msg: string|undefined);
1113 /** Defined and documented in `storage.js` */
1214 declare function nonundef(x: T|undefined, msg: string|undefined);
15 interface PromiseConstructor {
16 /**
17 * Polyfill
18 * @template T
19 */
20 withResolvers: function(): {
21 "promise": Promise<T>,
22 "resolve": (function(T): void),
23 "reject": (function(any): void)
24 };
25 }
1326 interface Window {
1427 /** Make the current theme easy to find */
1528 currentTheme: HTMLLinkElement|null;
......@@ -95,29 +108,28 @@ declare namespace rustdoc {
95108 interface SearchState {
96109 rustdocToolbar: HTMLElement|null;
97110 loadingText: string;
98 input: HTMLInputElement|null;
111 inputElement: function(): HTMLInputElement|null;
112 containerElement: function(): Element|null;
99113 title: string;
100114 titleBeforeSearch: string;
101 timeout: number|null;
115 timeout: ReturnType<typeof setTimeout>|null;
102116 currentTab: number;
103 focusedByTab: [number|null, number|null, number|null];
117 focusedByTab: [Element|null, Element|null, Element|null];
104118 clearInputTimeout: function;
105 outputElement(): HTMLElement|null;
106 focus();
107 defocus();
108 // note: an optional param is not the same as
109 // a nullable/undef-able param.
110 showResults(elem?: HTMLElement|null);
111 removeQueryParameters();
112 hideResults();
113 getQueryStringParams(): Object.<any, string>;
114 origPlaceholder: string;
119 outputElement: function(): Element|null;
120 focus: function();
121 defocus: function();
122 toggle: function();
123 showResults: function();
124 removeQueryParameters: function();
125 hideResults: function();
126 getQueryStringParams: function(): Object.<any, string>;
115127 setup: function();
116128 setLoadingSearch();
117129 descShards: Map<string, SearchDescShard[]>;
118130 loadDesc: function({descShard: SearchDescShard, descIndex: number}): Promise<string|null>;
119 loadedDescShard(string, number, string);
120 isDisplayed(): boolean,
131 loadedDescShard: function(string, number, string);
132 isDisplayed: function(): boolean;
121133 }
122134
123135 interface SearchDescShard {
......@@ -131,12 +143,13 @@ declare namespace rustdoc {
131143 * A single parsed "atom" in a search query. For example,
132144 *
133145 * std::fmt::Formatter, Write -> Result<()>
134 * ┏━━━━━━━━━━━━━━━━━━ ┌──── ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
135 * ┃ │ ┗ QueryElement { ┊
136 * ┃ │ name: Result ┊
137 * ┃ │ generics: [ ┊
138 * ┃ │ QueryElement ┘
139 * ┃ │ name: ()
146 * ┏━━━━━━━━━━━━━━━━━━ ┌──── ┏━━━━━┅┅┅┅┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┐
147 * ┃ │ ┗ QueryElement { ┊
148 * ┃ │ name: Result ┊
149 * ┃ │ generics: [ ┊
150 * ┃ │ QueryElement { ┘
151 * ┃ │ name: ()
152 * ┃ │ }
140153 * ┃ │ ]
141154 * ┃ │ }
142155 * ┃ └ QueryElement {
......@@ -156,14 +169,14 @@ declare namespace rustdoc {
156169 normalizedPathLast: string,
157170 generics: Array<QueryElement>,
158171 bindings: Map<number, Array<QueryElement>>,
159 typeFilter: number|null,
172 typeFilter: number,
160173 }
161174
162175 /**
163176 * Same as QueryElement, but bindings and typeFilter support strings
164177 */
165178 interface ParserQueryElement {
166 name: string|null,
179 name: string,
167180 id: number|null,
168181 fullPath: Array<string>,
169182 pathWithoutLast: Array<string>,
......@@ -172,7 +185,7 @@ declare namespace rustdoc {
172185 generics: Array<ParserQueryElement>,
173186 bindings: Map<string, Array<ParserQueryElement>>,
174187 bindingName: {name: string|null, generics: ParserQueryElement[]}|null,
175 typeFilter: number|string|null,
188 typeFilter: string|null,
176189 }
177190
178191 /**
......@@ -215,35 +228,74 @@ declare namespace rustdoc {
215228 /**
216229 * An entry in the search index database.
217230 */
231 interface EntryData {
232 krate: number,
233 ty: ItemType,
234 modulePath: number?,
235 exactModulePath: number?,
236 parent: number?,
237 deprecated: boolean,
238 associatedItemDisambiguator: string?,
239 }
240
241 /**
242 * A path in the search index database
243 */
244 interface PathData {
245 ty: ItemType,
246 modulePath: string,
247 exactModulePath: string?,
248 }
249
250 /**
251 * A function signature in the search index database
252 *
253 * Note that some non-function items (eg. constants, struct fields) have a function signature so they can appear in type-based search.
254 */
255 interface FunctionData {
256 functionSignature: FunctionSearchType|null,
257 paramNames: string[],
258 elemCount: number,
259 }
260
261 /**
262 * A function signature in the search index database
263 */
264 interface TypeData {
265 searchUnbox: boolean,
266 invertedFunctionSignatureIndex: RoaringBitmap[],
267 }
268
269 /**
270 * A search entry of some sort.
271 */
218272 interface Row {
219 crate: string,
220 descShard: SearchDescShard,
221273 id: number,
222 // This is the name of the item. For doc aliases, if you want the name of the aliased
223 // item, take a look at `Row.original.name`.
274 crate: string,
275 ty: ItemType,
224276 name: string,
225277 normalizedName: string,
226 word: string,
227 paramNames: string[],
228 parent: ({ty: number, name: string, path: string, exactPath: string}|null|undefined),
229 path: string,
230 ty: number,
231 type: FunctionSearchType | null,
232 descIndex: number,
233 bitIndex: number,
234 implDisambiguator: String | null,
235 is_alias?: boolean,
236 original?: Row,
278 modulePath: string,
279 exactModulePath: string,
280 entry: EntryData?,
281 path: PathData?,
282 type: FunctionData?,
283 deprecated: boolean,
284 parent: { path: PathData, name: string}?,
237285 }
238286
287 type ItemType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
288 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
289 21 | 22 | 23 | 24 | 25 | 26;
290
239291 /**
240292 * The viewmodel for the search engine results page.
241293 */
242294 interface ResultsTable {
243 in_args: Array<ResultObject>,
244 returned: Array<ResultObject>,
245 others: Array<ResultObject>,
246 query: ParsedQuery,
295 in_args: AsyncGenerator<ResultObject>,
296 returned: AsyncGenerator<ResultObject>,
297 others: AsyncGenerator<ResultObject>,
298 query: ParsedQuery<rustdoc.ParserQueryElement>,
247299 }
248300
249301 type Results = { max_dist?: number } & Map<number, ResultObject>
......@@ -252,25 +304,41 @@ declare namespace rustdoc {
252304 * An annotated `Row`, used in the viewmodel.
253305 */
254306 interface ResultObject {
255 desc: string,
307 desc: Promise<string|null>,
256308 displayPath: string,
257309 fullPath: string,
258310 href: string,
259311 id: number,
260312 dist: number,
261313 path_dist: number,
262 name: string,
263 normalizedName: string,
264 word: string,
265314 index: number,
266 parent: (Object|undefined),
267 path: string,
268 ty: number,
315 parent: ({
316 path: string,
317 exactPath: string,
318 name: string,
319 ty: number,
320 }|undefined),
269321 type?: FunctionSearchType,
270322 paramNames?: string[],
271323 displayTypeSignature: Promise<rustdoc.DisplayTypeSignature> | null,
272324 item: Row,
273 dontValidate?: boolean,
325 is_alias: boolean,
326 alias?: string,
327 }
328
329 /**
330 * An annotated `Row`, used in the viewmodel.
331 */
332 interface PlainResultObject {
333 id: number,
334 dist: number,
335 path_dist: number,
336 index: number,
337 elems: rustdoc.QueryElement[],
338 returned: rustdoc.QueryElement[],
339 is_alias: boolean,
340 alias?: string,
341 original?: rustdoc.Rlow,
274342 }
275343
276344 /**
......@@ -364,7 +432,19 @@ declare namespace rustdoc {
364432 * Numeric IDs are *ONE-indexed* into the paths array (`p`). Zero is used as a sentinel for `null`
365433 * because `null` is four bytes while `0` is one byte.
366434 */
367 type RawFunctionType = number | [number, Array<RawFunctionType>];
435 type RawFunctionType = number | [number, Array<RawFunctionType>] | [number, Array<RawFunctionType>, Array<[RawFunctionType, RawFunctionType[]]>];
436
437 /**
438 * Utility typedef for deserializing compact JSON.
439 *
440 * R is the required part, O is the optional part, which goes afterward.
441 * For example, `ArrayWithOptionals<[A, B], [C, D]>` matches
442 * `[A, B] | [A, B, C] | [A, B, C, D]`.
443 */
444 type ArrayWithOptionals<R extends any[], O extends any[]> =
445 O extends [infer First, ...infer Rest] ?
446 R | ArrayWithOptionals<[...R, First], Rest> :
447 R;
368448
369449 /**
370450 * The type signature entry in the decoded search index.
......@@ -382,8 +462,8 @@ declare namespace rustdoc {
382462 */
383463 interface FunctionType {
384464 id: null|number,
385 ty: number|null,
386 name?: string,
465 ty: ItemType,
466 name: string|null,
387467 path: string|null,
388468 exactPath: string|null,
389469 unboxFlag: boolean,
......@@ -403,70 +483,6 @@ declare namespace rustdoc {
403483 bindings: Map<number, FingerprintableType[]>;
404484 };
405485
406 /**
407 * The raw search data for a given crate. `n`, `t`, `d`, `i`, and `f`
408 * are arrays with the same length. `q`, `a`, and `c` use a sparse
409 * representation for compactness.
410 *
411 * `n[i]` contains the name of an item.
412 *
413 * `t[i]` contains the type of that item
414 * (as a string of characters that represent an offset in `itemTypes`).
415 *
416 * `d[i]` contains the description of that item.
417 *
418 * `q` contains the full paths of the items. For compactness, it is a set of
419 * (index, path) pairs used to create a map. If a given index `i` is
420 * not present, this indicates "same as the last index present".
421 *
422 * `i[i]` contains an item's parent, usually a module. For compactness,
423 * it is a set of indexes into the `p` array.
424 *
425 * `f` contains function signatures, or `0` if the item isn't a function.
426 * More information on how they're encoded can be found in rustc-dev-guide
427 *
428 * Functions are themselves encoded as arrays. The first item is a list of
429 * types representing the function's inputs, and the second list item is a list
430 * of types representing the function's output. Tuples are flattened.
431 * Types are also represented as arrays; the first item is an index into the `p`
432 * array, while the second is a list of types representing any generic parameters.
433 *
434 * b[i] contains an item's impl disambiguator. This is only present if an item
435 * is defined in an impl block and, the impl block's type has more than one associated
436 * item with the same name.
437 *
438 * `a` defines aliases with an Array of pairs: [name, offset], where `offset`
439 * points into the n/t/d/q/i/f arrays.
440 *
441 * `doc` contains the description of the crate.
442 *
443 * `p` is a list of path/type pairs. It is used for parents and function parameters.
444 * The first item is the type, the second is the name, the third is the visible path (if any) and
445 * the fourth is the canonical path used for deduplication (if any).
446 *
447 * `r` is the canonical path used for deduplication of re-exported items.
448 * It is not used for associated items like methods (that's the fourth element
449 * of `p`) but is used for modules items like free functions.
450 *
451 * `c` is an array of item indices that are deprecated.
452 */
453 type RawSearchIndexCrate = {
454 doc: string,
455 a: { [key: string]: number[] },
456 n: Array<string>,
457 t: string,
458 D: string,
459 e: string,
460 q: Array<[number, string]>,
461 i: string,
462 f: string,
463 p: Array<[number, string] | [number, string, number] | [number, string, number, number] | [number, string, number, number, string]>,
464 b: Array<[number, String]>,
465 c: string,
466 r: Array<[number, number]>,
467 P: Array<[number, string]>,
468 };
469
470486 type VlqData = VlqData[] | number;
471487
472488 /**
src/librustdoc/html/static/js/search.js+2317-2930
......@@ -1,9 +1,16 @@
11// ignore-tidy-filelength
2/* global addClass, getNakedUrl, getSettingValue, getVar */
3/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi, exports */
2/* global addClass, getNakedUrl, getVar, nonnull, getSettingValue */
3/* global onEachLazy, removeClass, searchState, browserSupportsHistoryApi */
44
55"use strict";
66
7/**
8 * @param {stringdex.Stringdex} Stringdex
9 * @param {typeof stringdex.RoaringBitmap} RoaringBitmap
10 * @param {stringdex.Hooks} hooks
11 */
12const initSearch = async function(Stringdex, RoaringBitmap, hooks) {
13
714// polyfill
815// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced
916if (!Array.prototype.toSpliced) {
......@@ -20,31 +27,65 @@ if (!Array.prototype.toSpliced) {
2027 *
2128 * @template T
2229 * @param {Iterable<T>} arr
23 * @param {function(T): any} func
30 * @param {function(T): Promise<any>} func
2431 * @param {function(T): boolean} funcBtwn
2532 */
26function onEachBtwn(arr, func, funcBtwn) {
33async function onEachBtwnAsync(arr, func, funcBtwn) {
2734 let skipped = true;
2835 for (const value of arr) {
2936 if (!skipped) {
3037 funcBtwn(value);
3138 }
32 skipped = func(value);
39 skipped = await func(value);
3340 }
3441}
3542
3643/**
37 * Convert any `undefined` to `null`.
38 *
39 * @template T
40 * @param {T|undefined} x
41 * @returns {T|null}
44 * Allow the browser to redraw.
45 * @returns {Promise<void>}
4246 */
43function undef2null(x) {
44 if (x !== undefined) {
45 return x;
46 }
47 return null;
47const yieldToBrowser = typeof window !== "undefined" && window.requestIdleCallback ?
48 function() {
49 return new Promise((resolve, _reject) => {
50 window.requestIdleCallback(resolve);
51 });
52 } :
53 function() {
54 return new Promise((resolve, _reject) => {
55 setTimeout(resolve, 0);
56 });
57 };
58
59/**
60 * Promise-based timer wrapper.
61 * @param {number} ms
62 * @returns {Promise<void>}
63 */
64const timeout = function(ms) {
65 return new Promise((resolve, _reject) => {
66 setTimeout(resolve, ms);
67 });
68};
69
70if (!Promise.withResolvers) {
71 /**
72 * Polyfill
73 * @template T
74 * @returns {{
75 "promise": Promise<T>,
76 "resolve": (function(T): void),
77 "reject": (function(any): void)
78 }}
79 */
80 Promise.withResolvers = () => {
81 let resolve, reject;
82 const promise = new Promise((res, rej) => {
83 resolve = res;
84 reject = rej;
85 });
86 // @ts-expect-error
87 return {promise, resolve, reject};
88 };
4889}
4990
5091// ==================== Core search logic begin ====================
......@@ -81,13 +122,22 @@ const itemTypes = [
81122];
82123
83124// used for special search precedence
84const TY_PRIMITIVE = itemTypes.indexOf("primitive");
85const TY_GENERIC = itemTypes.indexOf("generic");
86const TY_IMPORT = itemTypes.indexOf("import");
87const TY_TRAIT = itemTypes.indexOf("trait");
88const TY_FN = itemTypes.indexOf("fn");
89const TY_METHOD = itemTypes.indexOf("method");
90const TY_TYMETHOD = itemTypes.indexOf("tymethod");
125/** @type {rustdoc.ItemType} */
126const TY_PRIMITIVE = 1;
127/** @type {rustdoc.ItemType} */
128const TY_GENERIC = 26;
129/** @type {rustdoc.ItemType} */
130const TY_IMPORT = 4;
131/** @type {rustdoc.ItemType} */
132const TY_TRAIT = 10;
133/** @type {rustdoc.ItemType} */
134const TY_FN = 7;
135/** @type {rustdoc.ItemType} */
136const TY_METHOD = 13;
137/** @type {rustdoc.ItemType} */
138const TY_TYMETHOD = 12;
139/** @type {rustdoc.ItemType} */
140const TY_ASSOCTYPE = 17;
91141const ROOT_PATH = typeof window !== "undefined" ? window.rootPath : "../";
92142
93143// Hard limit on how deep to recurse into generics when doing type-driven search.
......@@ -242,7 +292,9 @@ function isEndCharacter(c) {
242292}
243293
244294/**
245 * @param {number} ty
295 * Same thing as ItemType::is_fn_like in item_type.rs
296 *
297 * @param {rustdoc.ItemType} ty
246298 * @returns
247299 */
248300function isFnLikeTy(ty) {
......@@ -1023,6 +1075,7 @@ class VlqHexDecoder {
10231075 this.string = string;
10241076 this.cons = cons;
10251077 this.offset = 0;
1078 this.elemCount = 0;
10261079 /** @type {T[]} */
10271080 this.backrefQueue = [];
10281081 }
......@@ -1060,6 +1113,7 @@ class VlqHexDecoder {
10601113 n = (n << 4) | (c & 0xF);
10611114 const [sign, value] = [n & 1, n >> 1];
10621115 this.offset += 1;
1116 this.elemCount += 1;
10631117 return sign ? -value : value;
10641118 }
10651119 /**
......@@ -1086,1303 +1140,159 @@ class VlqHexDecoder {
10861140 return result;
10871141 }
10881142}
1089class RoaringBitmap {
1090 /** @param {string} str */
1091 constructor(str) {
1092 // https://github.com/RoaringBitmap/RoaringFormatSpec
1093 //
1094 // Roaring bitmaps are used for flags that can be kept in their
1095 // compressed form, even when loaded into memory. This decoder
1096 // turns the containers into objects, but uses byte array
1097 // slices of the original format for the data payload.
1098 const strdecoded = atob(str);
1099 const u8array = new Uint8Array(strdecoded.length);
1100 for (let j = 0; j < strdecoded.length; ++j) {
1101 u8array[j] = strdecoded.charCodeAt(j);
1102 }
1103 const has_runs = u8array[0] === 0x3b;
1104 const size = has_runs ?
1105 ((u8array[2] | (u8array[3] << 8)) + 1) :
1106 ((u8array[4] | (u8array[5] << 8) | (u8array[6] << 16) | (u8array[7] << 24)));
1107 let i = has_runs ? 4 : 8;
1108 let is_run;
1109 if (has_runs) {
1110 const is_run_len = Math.floor((size + 7) / 8);
1111 is_run = u8array.slice(i, i + is_run_len);
1112 i += is_run_len;
1113 } else {
1114 is_run = new Uint8Array();
1115 }
1116 this.keys = [];
1117 this.cardinalities = [];
1118 for (let j = 0; j < size; ++j) {
1119 this.keys.push(u8array[i] | (u8array[i + 1] << 8));
1120 i += 2;
1121 this.cardinalities.push((u8array[i] | (u8array[i + 1] << 8)) + 1);
1122 i += 2;
1123 }
1124 this.containers = [];
1125 let offsets = null;
1126 if (!has_runs || this.keys.length >= 4) {
1127 offsets = [];
1128 for (let j = 0; j < size; ++j) {
1129 offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) |
1130 (u8array[i + 3] << 24));
1131 i += 4;
1132 }
1133 }
1134 for (let j = 0; j < size; ++j) {
1135 if (offsets && offsets[j] !== i) {
1136 // eslint-disable-next-line no-console
1137 console.log(this.containers);
1138 throw new Error(`corrupt bitmap ${j}: ${i} / ${offsets[j]}`);
1139 }
1140 if (is_run[j >> 3] & (1 << (j & 0x7))) {
1141 const runcount = (u8array[i] | (u8array[i + 1] << 8));
1142 i += 2;
1143 this.containers.push(new RoaringBitmapRun(
1144 runcount,
1145 u8array.slice(i, i + (runcount * 4)),
1146 ));
1147 i += runcount * 4;
1148 } else if (this.cardinalities[j] >= 4096) {
1149 this.containers.push(new RoaringBitmapBits(u8array.slice(i, i + 8192)));
1150 i += 8192;
1151 } else {
1152 const end = this.cardinalities[j] * 2;
1153 this.containers.push(new RoaringBitmapArray(
1154 this.cardinalities[j],
1155 u8array.slice(i, i + end),
1156 ));
1157 i += end;
1158 }
1159 }
1160 }
1161 /** @param {number} keyvalue */
1162 contains(keyvalue) {
1163 const key = keyvalue >> 16;
1164 const value = keyvalue & 0xFFFF;
1165 // Binary search algorithm copied from
1166 // https://en.wikipedia.org/wiki/Binary_search#Procedure
1167 //
1168 // Format is required by specification to be sorted.
1169 // Because keys are 16 bits and unique, length can't be
1170 // bigger than 2**16, and because we have 32 bits of safe int,
1171 // left + right can't overflow.
1172 let left = 0;
1173 let right = this.keys.length - 1;
1174 while (left <= right) {
1175 const mid = Math.floor((left + right) / 2);
1176 const x = this.keys[mid];
1177 if (x < key) {
1178 left = mid + 1;
1179 } else if (x > key) {
1180 right = mid - 1;
1181 } else {
1182 return this.containers[mid].contains(value);
1183 }
1184 }
1185 return false;
1186 }
1187}
11881143
1189class RoaringBitmapRun {
1190 /**
1191 * @param {number} runcount
1192 * @param {Uint8Array} array
1193 */
1194 constructor(runcount, array) {
1195 this.runcount = runcount;
1196 this.array = array;
1197 }
1198 /** @param {number} value */
1199 contains(value) {
1200 // Binary search algorithm copied from
1201 // https://en.wikipedia.org/wiki/Binary_search#Procedure
1202 //
1203 // Since runcount is stored as 16 bits, left + right
1204 // can't overflow.
1205 let left = 0;
1206 let right = this.runcount - 1;
1207 while (left <= right) {
1208 const mid = Math.floor((left + right) / 2);
1209 const i = mid * 4;
1210 const start = this.array[i] | (this.array[i + 1] << 8);
1211 const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8);
1212 if ((start + lenm1) < value) {
1213 left = mid + 1;
1214 } else if (start > value) {
1215 right = mid - 1;
1216 } else {
1217 return true;
1218 }
1219 }
1220 return false;
1221 }
1222}
1223class RoaringBitmapArray {
1224 /**
1225 * @param {number} cardinality
1226 * @param {Uint8Array} array
1227 */
1228 constructor(cardinality, array) {
1229 this.cardinality = cardinality;
1230 this.array = array;
1231 }
1232 /** @param {number} value */
1233 contains(value) {
1234 // Binary search algorithm copied from
1235 // https://en.wikipedia.org/wiki/Binary_search#Procedure
1236 //
1237 // Since cardinality can't be higher than 4096, left + right
1238 // cannot overflow.
1239 let left = 0;
1240 let right = this.cardinality - 1;
1241 while (left <= right) {
1242 const mid = Math.floor((left + right) / 2);
1243 const i = mid * 2;
1244 const x = this.array[i] | (this.array[i + 1] << 8);
1245 if (x < value) {
1246 left = mid + 1;
1247 } else if (x > value) {
1248 right = mid - 1;
1249 } else {
1250 return true;
1251 }
1252 }
1253 return false;
1254 }
1255}
1256class RoaringBitmapBits {
1257 /**
1258 * @param {Uint8Array} array
1259 */
1260 constructor(array) {
1261 this.array = array;
1262 }
1263 /** @param {number} value */
1264 contains(value) {
1265 return !!(this.array[value >> 3] & (1 << (value & 7)));
1266 }
1267}
1144/** @type {Array<string>} */
1145const EMPTY_STRING_ARRAY = [];
1146
1147/** @type {Array<rustdoc.FunctionType>} */
1148const EMPTY_GENERICS_ARRAY = [];
1149
1150/** @type {Array<[number, rustdoc.FunctionType[]]>} */
1151const EMPTY_BINDINGS_ARRAY = [];
1152
1153/** @type {Map<number, Array<any>>} */
1154const EMPTY_BINDINGS_MAP = new Map();
12681155
12691156/**
1270 * A prefix tree, used for name-based search.
1271 *
1272 * This data structure is used to drive prefix matches,
1273 * such as matching the query "link" to `LinkedList`,
1274 * and Lev-distance matches, such as matching the
1275 * query "hahsmap" to `HashMap`. Substring matches,
1276 * such as "list" to `LinkedList`, are done with a
1277 * tailTable that deep-links into this trie.
1278 *
1279 * children
1280 * : A [sparse array] of subtrees. The array index
1281 * is a charCode.
1282 *
1283 * [sparse array]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/
1284 * Indexed_collections#sparse_arrays
1285 *
1286 * matches
1287 * : A list of search index IDs for this node.
1288 *
1289 * @type {{
1290 * children: NameTrie[],
1291 * matches: number[],
1292 * }}
1157 * @param {string|null} typename
1158 * @returns {number}
12931159 */
1294class NameTrie {
1295 constructor() {
1296 this.children = [];
1297 this.matches = [];
1298 }
1299 /**
1300 * @param {string} name
1301 * @param {number} id
1302 * @param {Map<string, NameTrie[]>} tailTable
1303 */
1304 insert(name, id, tailTable) {
1305 this.insertSubstring(name, 0, id, tailTable);
1306 }
1307 /**
1308 * @param {string} name
1309 * @param {number} substart
1310 * @param {number} id
1311 * @param {Map<string, NameTrie[]>} tailTable
1312 */
1313 insertSubstring(name, substart, id, tailTable) {
1314 const l = name.length;
1315 if (substart === l) {
1316 this.matches.push(id);
1317 } else {
1318 const sb = name.charCodeAt(substart);
1319 let child;
1320 if (this.children[sb] !== undefined) {
1321 child = this.children[sb];
1322 } else {
1323 child = new NameTrie();
1324 this.children[sb] = child;
1325 /** @type {NameTrie[]} */
1326 let sste;
1327 if (substart >= 2) {
1328 const tail = name.substring(substart - 2, substart + 1);
1329 const entry = tailTable.get(tail);
1330 if (entry !== undefined) {
1331 sste = entry;
1332 } else {
1333 sste = [];
1334 tailTable.set(tail, sste);
1335 }
1336 sste.push(child);
1337 }
1338 }
1339 child.insertSubstring(name, substart + 1, id, tailTable);
1340 }
1341 }
1342 /**
1343 * @param {string} name
1344 * @param {Map<string, NameTrie[]>} tailTable
1345 */
1346 search(name, tailTable) {
1347 const results = new Set();
1348 this.searchSubstringPrefix(name, 0, results);
1349 if (results.size < MAX_RESULTS && name.length >= 3) {
1350 const levParams = name.length >= 6 ?
1351 new Lev2TParametricDescription(name.length) :
1352 new Lev1TParametricDescription(name.length);
1353 this.searchLev(name, 0, levParams, results);
1354 const tail = name.substring(0, 3);
1355 const list = tailTable.get(tail);
1356 if (list !== undefined) {
1357 for (const entry of list) {
1358 entry.searchSubstringPrefix(name, 3, results);
1359 }
1360 }
1361 }
1362 return [...results];
1363 }
1364 /**
1365 * @param {string} name
1366 * @param {number} substart
1367 * @param {Set<number>} results
1368 */
1369 searchSubstringPrefix(name, substart, results) {
1370 const l = name.length;
1371 if (substart === l) {
1372 for (const match of this.matches) {
1373 results.add(match);
1374 }
1375 // breadth-first traversal orders prefix matches by length
1376 /** @type {NameTrie[]} */
1377 let unprocessedChildren = [];
1378 for (const child of this.children) {
1379 if (child) {
1380 unprocessedChildren.push(child);
1381 }
1382 }
1383 /** @type {NameTrie[]} */
1384 let nextSet = [];
1385 while (unprocessedChildren.length !== 0) {
1386 /** @type {NameTrie} */
1387 // @ts-expect-error
1388 const next = unprocessedChildren.pop();
1389 for (const child of next.children) {
1390 if (child) {
1391 nextSet.push(child);
1392 }
1393 }
1394 for (const match of next.matches) {
1395 results.add(match);
1396 }
1397 if (unprocessedChildren.length === 0) {
1398 const tmp = unprocessedChildren;
1399 unprocessedChildren = nextSet;
1400 nextSet = tmp;
1401 }
1402 }
1403 } else {
1404 const sb = name.charCodeAt(substart);
1405 if (this.children[sb] !== undefined) {
1406 this.children[sb].searchSubstringPrefix(name, substart + 1, results);
1407 }
1408 }
1160function itemTypeFromName(typename) {
1161 if (typename === null) {
1162 return NO_TYPE_FILTER;
14091163 }
1410 /**
1411 * @param {string} name
1412 * @param {number} substart
1413 * @param {Lev2TParametricDescription|Lev1TParametricDescription} levParams
1414 * @param {Set<number>} results
1415 */
1416 searchLev(name, substart, levParams, results) {
1417 const stack = [[this, 0]];
1418 const n = levParams.n;
1419 while (stack.length !== 0) {
1420 // It's not empty
1421 //@ts-expect-error
1422 const [trie, levState] = stack.pop();
1423 for (const [charCode, child] of trie.children.entries()) {
1424 if (!child) {
1425 continue;
1426 }
1427 const levPos = levParams.getPosition(levState);
1428 const vector = levParams.getVector(
1429 name,
1430 charCode,
1431 levPos,
1432 Math.min(name.length, levPos + (2 * n) + 1),
1433 );
1434 const newLevState = levParams.transition(
1435 levState,
1436 levPos,
1437 vector,
1438 );
1439 if (newLevState >= 0) {
1440 stack.push([child, newLevState]);
1441 if (levParams.isAccept(newLevState)) {
1442 for (const match of child.matches) {
1443 results.add(match);
1444 }
1445 }
1446 }
1447 }
1448 }
1164 const index = itemTypes.findIndex(i => i === typename);
1165 if (index < 0) {
1166 throw ["Unknown type filter ", typename];
14491167 }
1168 return index;
14501169}
14511170
14521171class DocSearch {
14531172 /**
1454 * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex
14551173 * @param {string} rootPath
1456 * @param {rustdoc.SearchState} searchState
1174 * @param {stringdex.Database} database
14571175 */
1458 constructor(rawSearchIndex, rootPath, searchState) {
1459 /**
1460 * @type {Map<String, RoaringBitmap>}
1461 */
1462 this.searchIndexDeprecated = new Map();
1463 /**
1464 * @type {Map<String, RoaringBitmap>}
1465 */
1466 this.searchIndexEmptyDesc = new Map();
1467 /**
1468 * @type {Uint32Array}
1469 */
1470 this.functionTypeFingerprint = new Uint32Array(0);
1471 /**
1472 * Map from normalized type names to integers. Used to make type search
1473 * more efficient.
1474 *
1475 * @type {Map<string, {id: number, assocOnly: boolean}>}
1476 */
1477 this.typeNameIdMap = new Map();
1478 /**
1479 * Map from type ID to associated type name. Used for display,
1480 * not for search.
1481 *
1482 * @type {Map<number, string>}
1483 */
1484 this.assocTypeIdNameMap = new Map();
1485 this.ALIASES = new Map();
1486 this.FOUND_ALIASES = new Set();
1176 constructor(rootPath, database) {
14871177 this.rootPath = rootPath;
1488 this.searchState = searchState;
1489
1490 /**
1491 * Special type name IDs for searching by array.
1492 * @type {number}
1493 */
1494 this.typeNameIdOfArray = this.buildTypeMapIndex("array");
1495 /**
1496 * Special type name IDs for searching by slice.
1497 * @type {number}
1498 */
1499 this.typeNameIdOfSlice = this.buildTypeMapIndex("slice");
1500 /**
1501 * Special type name IDs for searching by both array and slice (`[]` syntax).
1502 * @type {number}
1503 */
1504 this.typeNameIdOfArrayOrSlice = this.buildTypeMapIndex("[]");
1505 /**
1506 * Special type name IDs for searching by tuple.
1507 * @type {number}
1508 */
1509 this.typeNameIdOfTuple = this.buildTypeMapIndex("tuple");
1510 /**
1511 * Special type name IDs for searching by unit.
1512 * @type {number}
1513 */
1514 this.typeNameIdOfUnit = this.buildTypeMapIndex("unit");
1515 /**
1516 * Special type name IDs for searching by both tuple and unit (`()` syntax).
1517 * @type {number}
1518 */
1519 this.typeNameIdOfTupleOrUnit = this.buildTypeMapIndex("()");
1520 /**
1521 * Special type name IDs for searching `fn`.
1522 * @type {number}
1523 */
1524 this.typeNameIdOfFn = this.buildTypeMapIndex("fn");
1525 /**
1526 * Special type name IDs for searching `fnmut`.
1527 * @type {number}
1528 */
1529 this.typeNameIdOfFnMut = this.buildTypeMapIndex("fnmut");
1530 /**
1531 * Special type name IDs for searching `fnonce`.
1532 * @type {number}
1533 */
1534 this.typeNameIdOfFnOnce = this.buildTypeMapIndex("fnonce");
1535 /**
1536 * Special type name IDs for searching higher order functions (`->` syntax).
1537 * @type {number}
1538 */
1539 this.typeNameIdOfHof = this.buildTypeMapIndex("->");
1540 /**
1541 * Special type name IDs the output assoc type.
1542 * @type {number}
1543 */
1544 this.typeNameIdOfOutput = this.buildTypeMapIndex("output", true);
1545 /**
1546 * Special type name IDs for searching by reference.
1547 * @type {number}
1548 */
1549 this.typeNameIdOfReference = this.buildTypeMapIndex("reference");
1178 this.database = database;
15501179
1551 /**
1552 * Empty, immutable map used in item search types with no bindings.
1553 *
1554 * @type {Map<number, Array<any>>}
1555 */
1556 this.EMPTY_BINDINGS_MAP = new Map();
1180 this.typeNameIdOfOutput = -1;
1181 this.typeNameIdOfArray = -1;
1182 this.typeNameIdOfSlice = -1;
1183 this.typeNameIdOfArrayOrSlice = -1;
1184 this.typeNameIdOfTuple = -1;
1185 this.typeNameIdOfUnit = -1;
1186 this.typeNameIdOfTupleOrUnit = -1;
1187 this.typeNameIdOfReference = -1;
1188 this.typeNameIdOfHof = -1;
15571189
1558 /**
1559 * Empty, immutable map used in item search types with no bindings.
1560 *
1561 * @type {Array<any>}
1562 */
1563 this.EMPTY_GENERICS_ARRAY = [];
1190 this.utf8decoder = new TextDecoder();
15641191
1565 /**
1566 * Object pool for function types with no bindings or generics.
1567 * This is reset after loading the index.
1568 *
1569 * @type {Map<number|null, rustdoc.FunctionType>}
1570 */
1192 /** @type {Map<number|null, rustdoc.FunctionType>} */
15711193 this.TYPES_POOL = new Map();
1572
1573 /**
1574 * A trie for finding items by name.
1575 * This is used for edit distance and prefix finding.
1576 *
1577 * @type {NameTrie}
1578 */
1579 this.nameTrie = new NameTrie();
1580
1581 /**
1582 * Find items by 3-substring. This is a map from three-char
1583 * prefixes into lists of subtries.
1584 */
1585 this.tailTable = new Map();
1586
1587 /**
1588 * @type {Array<rustdoc.Row>}
1589 */
1590 this.searchIndex = this.buildIndex(rawSearchIndex);
15911194 }
15921195
15931196 /**
1594 * Add an item to the type Name->ID map, or, if one already exists, use it.
1595 * Returns the number. If name is "" or null, return null (pure generic).
1596 *
1597 * This is effectively string interning, so that function matching can be
1598 * done more quickly. Two types with the same name but different item kinds
1599 * get the same ID.
1600 *
1601 * @template T extends string
1602 * @overload
1603 * @param {T} name
1604 * @param {boolean=} isAssocType - True if this is an assoc type
1605 * @returns {T extends "" ? null : number}
1606 *
1607 * @param {string} name
1608 * @param {boolean=} isAssocType
1609 * @returns {number | null}
1610 *
1197 * Load search index. If you do not call this function, `execQuery`
1198 * will never fulfill.
16111199 */
1612 buildTypeMapIndex(name, isAssocType) {
1613 if (name === "" || name === null) {
1614 return null;
1615 }
1616
1617 const obj = this.typeNameIdMap.get(name);
1618 if (obj !== undefined) {
1619 obj.assocOnly = !!(isAssocType && obj.assocOnly);
1620 return obj.id;
1621 } else {
1622 const id = this.typeNameIdMap.size;
1623 this.typeNameIdMap.set(name, { id, assocOnly: !!isAssocType });
1624 return id;
1200 async buildIndex() {
1201 const nn = this.database.getIndex("normalizedName");
1202 if (!nn) {
1203 return;
16251204 }
1205 // Each of these identifiers are used specially by
1206 // type-driven search.
1207 const [
1208 // output is the special associated type that goes
1209 // after the arrow: the type checker desugars
1210 // the path `Fn(a) -> b` into `Fn<Output=b, (a)>`
1211 output,
1212 // fn, fnmut, and fnonce all match `->`
1213 fn,
1214 fnMut,
1215 fnOnce,
1216 hof,
1217 // array and slice both match `[]`
1218 array,
1219 slice,
1220 arrayOrSlice,
1221 // tuple and unit both match `()`
1222 tuple,
1223 unit,
1224 tupleOrUnit,
1225 // reference matches `&`
1226 reference,
1227 // never matches `!`
1228 never,
1229 ] = await Promise.all([
1230 nn.search("output"),
1231 nn.search("fn"),
1232 nn.search("fnmut"),
1233 nn.search("fnonce"),
1234 nn.search("->"),
1235 nn.search("array"),
1236 nn.search("slice"),
1237 nn.search("[]"),
1238 nn.search("tuple"),
1239 nn.search("unit"),
1240 nn.search("()"),
1241 nn.search("reference"),
1242 nn.search("never"),
1243 ]);
1244 /**
1245 * @param {stringdex.Trie|null|undefined} trie
1246 * @param {rustdoc.ItemType} ty
1247 * @param {string} modulePath
1248 * @returns {Promise<number>}
1249 * */
1250 const first = async(trie, ty, modulePath) => {
1251 if (trie) {
1252 for (const id of trie.matches().entries()) {
1253 const pathData = await this.getPathData(id);
1254 if (pathData && pathData.ty === ty && pathData.modulePath === modulePath) {
1255 return id;
1256 }
1257 }
1258 }
1259 return -1;
1260 };
1261 this.typeNameIdOfOutput = await first(output, TY_ASSOCTYPE, "");
1262 this.typeNameIdOfFnPtr = await first(fn, TY_PRIMITIVE, "");
1263 this.typeNameIdOfFn = await first(fn, TY_TRAIT, "core::ops");
1264 this.typeNameIdOfFnMut = await first(fnMut, TY_TRAIT, "core::ops");
1265 this.typeNameIdOfFnOnce = await first(fnOnce, TY_TRAIT, "core::ops");
1266 this.typeNameIdOfArray = await first(array, TY_PRIMITIVE, "");
1267 this.typeNameIdOfSlice = await first(slice, TY_PRIMITIVE, "");
1268 this.typeNameIdOfArrayOrSlice = await first(arrayOrSlice, TY_PRIMITIVE, "");
1269 this.typeNameIdOfTuple = await first(tuple, TY_PRIMITIVE, "");
1270 this.typeNameIdOfUnit = await first(unit, TY_PRIMITIVE, "");
1271 this.typeNameIdOfTupleOrUnit = await first(tupleOrUnit, TY_PRIMITIVE, "");
1272 this.typeNameIdOfReference = await first(reference, TY_PRIMITIVE, "");
1273 this.typeNameIdOfHof = await first(hof, TY_PRIMITIVE, "");
1274 this.typeNameIdOfNever = await first(never, TY_PRIMITIVE, "");
16261275 }
16271276
16281277 /**
1629 * Convert a list of RawFunctionType / ID to object-based FunctionType.
1630 *
1631 * Crates often have lots of functions in them, and it's common to have a large number of
1632 * functions that operate on a small set of data types, so the search index compresses them
1633 * by encoding function parameter and return types as indexes into an array of names.
1634 *
1635 * Even when a general-purpose compression algorithm is used, this is still a win.
1636 * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985
1278 * Parses the query.
16371279 *
1638 * The format for individual function types is encoded in
1639 * librustdoc/html/render/mod.rs: impl Serialize for RenderType
1280 * The supported syntax by this parser is given in the rustdoc book chapter
1281 * /src/doc/rustdoc/src/read-documentation/search.md
16401282 *
1641 * @param {null|Array<rustdoc.RawFunctionType>} types
1642 * @param {Array<{
1643 * name: string,
1644 * ty: number,
1645 * path: string|null,
1646 * exactPath: string|null,
1647 * unboxFlag: boolean
1648 * }>} paths
1649 * @param {Array<{
1650 * name: string,
1651 * ty: number,
1652 * path: string|null,
1653 * exactPath: string|null,
1654 * unboxFlag: boolean,
1655 * }>} lowercasePaths
1283 * When adding new things to the parser, add them there, too!
16561284 *
1657 * @return {Array<rustdoc.FunctionType>}
1658 */
1659 buildItemSearchTypeAll(types, paths, lowercasePaths) {
1660 return types && types.length > 0 ?
1661 types.map(type => this.buildItemSearchType(type, paths, lowercasePaths)) :
1662 this.EMPTY_GENERICS_ARRAY;
1663 }
1664
1665 /**
1666 * Converts a single type.
1285 * @param {string} userQuery - The user query
16671286 *
1668 * @param {rustdoc.RawFunctionType} type
1669 * @param {Array<{
1670 * name: string,
1671 * ty: number,
1672 * path: string|null,
1673 * exactPath: string|null,
1674 * unboxFlag: boolean
1675 * }>} paths
1676 * @param {Array<{
1677 * name: string,
1678 * ty: number,
1679 * path: string|null,
1680 * exactPath: string|null,
1681 * unboxFlag: boolean,
1682 * }>} lowercasePaths
1683 * @param {boolean=} isAssocType
1287 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query
16841288 */
1685 buildItemSearchType(type, paths, lowercasePaths, isAssocType) {
1686 const PATH_INDEX_DATA = 0;
1687 const GENERICS_DATA = 1;
1688 const BINDINGS_DATA = 2;
1689 let pathIndex, generics, bindings;
1690 if (typeof type === "number") {
1691 pathIndex = type;
1692 generics = this.EMPTY_GENERICS_ARRAY;
1693 bindings = this.EMPTY_BINDINGS_MAP;
1694 } else {
1695 pathIndex = type[PATH_INDEX_DATA];
1696 generics = this.buildItemSearchTypeAll(
1697 type[GENERICS_DATA],
1698 paths,
1699 lowercasePaths,
1700 );
1701 // @ts-expect-error
1702 if (type.length > BINDINGS_DATA && type[BINDINGS_DATA].length > 0) {
1703 // @ts-expect-error
1704 bindings = new Map(type[BINDINGS_DATA].map(binding => {
1705 const [assocType, constraints] = binding;
1706 // Associated type constructors are represented sloppily in rustdoc's
1707 // type search, to make the engine simpler.
1708 //
1709 // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
1710 // and both are, essentially
1711 // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
1712 // It's more like the value of a type binding is naturally an array,
1713 // which rustdoc calls "constraints".
1714 //
1715 // As a result, the key should never have generics on it.
1716 return [
1717 this.buildItemSearchType(assocType, paths, lowercasePaths, true).id,
1718 this.buildItemSearchTypeAll(constraints, paths, lowercasePaths),
1719 ];
1720 }));
1721 } else {
1722 bindings = this.EMPTY_BINDINGS_MAP;
1723 }
1724 }
1289 static parseQuery(userQuery) {
17251290 /**
1726 * @type {rustdoc.FunctionType}
1727 */
1728 let result;
1729 if (pathIndex < 0) {
1730 // types less than 0 are generic parameters
1731 // the actual names of generic parameters aren't stored, since they aren't API
1732 result = {
1733 id: pathIndex,
1734 name: "",
1735 ty: TY_GENERIC,
1736 path: null,
1737 exactPath: null,
1738 generics,
1739 bindings,
1740 unboxFlag: true,
1741 };
1742 } else if (pathIndex === 0) {
1743 // `0` is used as a sentinel because it's fewer bytes than `null`
1744 result = {
1745 id: null,
1746 name: "",
1747 ty: null,
1748 path: null,
1749 exactPath: null,
1750 generics,
1751 bindings,
1752 unboxFlag: true,
1753 };
1754 } else {
1755 const item = lowercasePaths[pathIndex - 1];
1756 const id = this.buildTypeMapIndex(item.name, isAssocType);
1757 if (isAssocType && id !== null) {
1758 this.assocTypeIdNameMap.set(id, paths[pathIndex - 1].name);
1759 }
1760 result = {
1761 id,
1762 name: paths[pathIndex - 1].name,
1763 ty: item.ty,
1764 path: item.path,
1765 exactPath: item.exactPath,
1766 generics,
1767 bindings,
1768 unboxFlag: item.unboxFlag,
1769 };
1770 }
1771 const cr = this.TYPES_POOL.get(result.id);
1772 if (cr) {
1773 // Shallow equality check. Since this function is used
1774 // to construct every type object, this should be mostly
1775 // equivalent to a deep equality check, except if there's
1776 // a conflict, we don't keep the old one around, so it's
1777 // not a fully precise implementation of hashcons.
1778 if (cr.generics.length === result.generics.length &&
1779 cr.generics !== result.generics &&
1780 cr.generics.every((x, i) => result.generics[i] === x)
1781 ) {
1782 result.generics = cr.generics;
1783 }
1784 if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) {
1785 let ok = true;
1786 for (const [k, v] of cr.bindings.entries()) {
1787 // @ts-expect-error
1788 const v2 = result.bindings.get(v);
1789 if (!v2) {
1790 ok = false;
1791 break;
1792 }
1793 if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) {
1794 result.bindings.set(k, v);
1795 } else if (v !== v2) {
1796 ok = false;
1797 break;
1798 }
1799 }
1800 if (ok) {
1801 result.bindings = cr.bindings;
1802 }
1803 }
1804 if (cr.ty === result.ty && cr.path === result.path
1805 && cr.bindings === result.bindings && cr.generics === result.generics
1806 && cr.ty === result.ty && cr.name === result.name
1807 && cr.unboxFlag === result.unboxFlag
1808 ) {
1809 return cr;
1810 }
1811 }
1812 this.TYPES_POOL.set(result.id, result);
1813 return result;
1814 }
1815
1816 /**
1817 * Type fingerprints allow fast, approximate matching of types.
1818 *
1819 * This algo creates a compact representation of the type set using a Bloom filter.
1820 * This fingerprint is used three ways:
1821 *
1822 * - It accelerates the matching algorithm by checking the function fingerprint against the
1823 * query fingerprint. If any bits are set in the query but not in the function, it can't
1824 * match.
1825 *
1826 * - The fourth section has the number of items in the set.
1827 * This is the distance function, used for filtering and for sorting.
1828 *
1829 * [^1]: Distance is the relatively naive metric of counting the number of distinct items in
1830 * the function that are not present in the query.
1831 *
1832 * @param {rustdoc.FingerprintableType} type - a single type
1833 * @param {Uint32Array} output - write the fingerprint to this data structure: uses 128 bits
1834 */
1835 buildFunctionTypeFingerprint(type, output) {
1836 let input = type.id;
1837 // All forms of `[]`/`()`/`->` get collapsed down to one thing in the bloom filter.
1838 // Differentiating between arrays and slices, if the user asks for it, is
1839 // still done in the matching algorithm.
1840 if (input === this.typeNameIdOfArray || input === this.typeNameIdOfSlice) {
1841 input = this.typeNameIdOfArrayOrSlice;
1842 }
1843 if (input === this.typeNameIdOfTuple || input === this.typeNameIdOfUnit) {
1844 input = this.typeNameIdOfTupleOrUnit;
1845 }
1846 if (input === this.typeNameIdOfFn || input === this.typeNameIdOfFnMut ||
1847 input === this.typeNameIdOfFnOnce) {
1848 input = this.typeNameIdOfHof;
1849 }
1850 /**
1851 * http://burtleburtle.net/bob/hash/integer.html
1852 * ~~ is toInt32. It's used before adding, so
1853 * the number stays in safe integer range.
1854 * @param {number} k
1855 */
1856 const hashint1 = k => {
1857 k = (~~k + 0x7ed55d16) + (k << 12);
1858 k = (k ^ 0xc761c23c) ^ (k >>> 19);
1859 k = (~~k + 0x165667b1) + (k << 5);
1860 k = (~~k + 0xd3a2646c) ^ (k << 9);
1861 k = (~~k + 0xfd7046c5) + (k << 3);
1862 return (k ^ 0xb55a4f09) ^ (k >>> 16);
1863 };
1864 /** @param {number} k */
1865 const hashint2 = k => {
1866 k = ~k + (k << 15);
1867 k ^= k >>> 12;
1868 k += k << 2;
1869 k ^= k >>> 4;
1870 k = Math.imul(k, 2057);
1871 return k ^ (k >> 16);
1872 };
1873 if (input !== null) {
1874 const h0a = hashint1(input);
1875 const h0b = hashint2(input);
1876 // Less Hashing, Same Performance: Building a Better Bloom Filter
1877 // doi=10.1.1.72.2442
1878 const h1a = ~~(h0a + Math.imul(h0b, 2));
1879 const h1b = ~~(h0a + Math.imul(h0b, 3));
1880 const h2a = ~~(h0a + Math.imul(h0b, 4));
1881 const h2b = ~~(h0a + Math.imul(h0b, 5));
1882 output[0] |= (1 << (h0a % 32)) | (1 << (h1b % 32));
1883 output[1] |= (1 << (h1a % 32)) | (1 << (h2b % 32));
1884 output[2] |= (1 << (h2a % 32)) | (1 << (h0b % 32));
1885 // output[3] is the total number of items in the type signature
1886 output[3] += 1;
1887 }
1888 for (const g of type.generics) {
1889 this.buildFunctionTypeFingerprint(g, output);
1890 }
1891 /**
1892 * @type {{
1893 * id: number|null,
1894 * ty: number,
1895 * generics: rustdoc.FingerprintableType[],
1896 * bindings: Map<number, rustdoc.FingerprintableType[]>
1897 * }}
1898 */
1899 const fb = {
1900 id: null,
1901 ty: 0,
1902 generics: this.EMPTY_GENERICS_ARRAY,
1903 bindings: this.EMPTY_BINDINGS_MAP,
1904 };
1905 for (const [k, v] of type.bindings.entries()) {
1906 fb.id = k;
1907 fb.generics = v;
1908 this.buildFunctionTypeFingerprint(fb, output);
1909 }
1910 }
1911
1912 /**
1913 * Convert raw search index into in-memory search index.
1914 *
1915 * @param {Map<string, rustdoc.RawSearchIndexCrate>} rawSearchIndex
1916 * @returns {rustdoc.Row[]}
1917 */
1918 buildIndex(rawSearchIndex) {
1919 /**
1920 * Convert from RawFunctionSearchType to FunctionSearchType.
1921 *
1922 * Crates often have lots of functions in them, and function signatures are sometimes
1923 * complex, so rustdoc uses a pretty tight encoding for them. This function converts it
1924 * to a simpler, object-based encoding so that the actual search code is more readable
1925 * and easier to debug.
1926 *
1927 * The raw function search type format is generated using serde in
1928 * librustdoc/html/render/mod.rs: IndexItemFunctionType::write_to_string
1929 *
1930 * @param {Array<{
1931 * name: string,
1932 * ty: number,
1933 * path: string|null,
1934 * exactPath: string|null,
1935 * unboxFlag: boolean
1936 * }>} paths
1937 * @param {Array<{
1938 * name: string,
1939 * ty: number,
1940 * path: string|null,
1941 * exactPath: string|null,
1942 * unboxFlag: boolean
1943 * }>} lowercasePaths
1944 *
1945 * @return {function(rustdoc.RawFunctionSearchType): null|rustdoc.FunctionSearchType}
1946 */
1947 const buildFunctionSearchTypeCallback = (paths, lowercasePaths) => {
1948 /**
1949 * @param {rustdoc.RawFunctionSearchType} functionSearchType
1950 */
1951 const cb = functionSearchType => {
1952 if (functionSearchType === 0) {
1953 return null;
1954 }
1955 const INPUTS_DATA = 0;
1956 const OUTPUT_DATA = 1;
1957 /** @type {rustdoc.FunctionType[]} */
1958 let inputs;
1959 /** @type {rustdoc.FunctionType[]} */
1960 let output;
1961 if (typeof functionSearchType[INPUTS_DATA] === "number") {
1962 inputs = [
1963 this.buildItemSearchType(
1964 functionSearchType[INPUTS_DATA],
1965 paths,
1966 lowercasePaths,
1967 ),
1968 ];
1969 } else {
1970 inputs = this.buildItemSearchTypeAll(
1971 functionSearchType[INPUTS_DATA],
1972 paths,
1973 lowercasePaths,
1974 );
1975 }
1976 if (functionSearchType.length > 1) {
1977 if (typeof functionSearchType[OUTPUT_DATA] === "number") {
1978 output = [
1979 this.buildItemSearchType(
1980 functionSearchType[OUTPUT_DATA],
1981 paths,
1982 lowercasePaths,
1983 ),
1984 ];
1985 } else {
1986 output = this.buildItemSearchTypeAll(
1987 // @ts-expect-error
1988 functionSearchType[OUTPUT_DATA],
1989 paths,
1990 lowercasePaths,
1991 );
1992 }
1993 } else {
1994 output = [];
1995 }
1996 const where_clause = [];
1997 const l = functionSearchType.length;
1998 for (let i = 2; i < l; ++i) {
1999 where_clause.push(typeof functionSearchType[i] === "number"
2000 // @ts-expect-error
2001 ? [this.buildItemSearchType(functionSearchType[i], paths, lowercasePaths)]
2002 : this.buildItemSearchTypeAll(
2003 // @ts-expect-error
2004 functionSearchType[i],
2005 paths,
2006 lowercasePaths,
2007 ));
2008 }
2009 return {
2010 inputs, output, where_clause,
2011 };
2012 };
2013 return cb;
2014 };
2015
2016 /** @type {rustdoc.Row[]} */
2017 const searchIndex = [];
2018 let currentIndex = 0;
2019 let id = 0;
2020
2021 // Function type fingerprints are 128-bit bloom filters that are used to
2022 // estimate the distance between function and query.
2023 // This loop counts the number of items to allocate a fingerprint for.
2024 for (const crate of rawSearchIndex.values()) {
2025 // Each item gets an entry in the fingerprint array, and the crate
2026 // does, too
2027 id += crate.t.length + 1;
2028 }
2029 this.functionTypeFingerprint = new Uint32Array((id + 1) * 4);
2030 // This loop actually generates the search item indexes, including
2031 // normalized names, type signature objects and fingerprints, and aliases.
2032 id = 0;
2033
2034 /** @type {Array<[string, { [key: string]: Array<number> }, number]>} */
2035 const allAliases = [];
2036 for (const [crate, crateCorpus] of rawSearchIndex) {
2037 // a string representing the lengths of each description shard
2038 // a string representing the list of function types
2039 const itemDescShardDecoder = new VlqHexDecoder(crateCorpus.D, noop => {
2040 /** @type {number} */
2041 // @ts-expect-error
2042 const n = noop;
2043 return n;
2044 });
2045 let descShard = {
2046 crate,
2047 shard: 0,
2048 start: 0,
2049 len: itemDescShardDecoder.next(),
2050 promise: null,
2051 resolve: null,
2052 };
2053 const descShardList = [descShard];
2054
2055 // Deprecated items and items with no description
2056 this.searchIndexDeprecated.set(crate, new RoaringBitmap(crateCorpus.c));
2057 this.searchIndexEmptyDesc.set(crate, new RoaringBitmap(crateCorpus.e));
2058 let descIndex = 0;
2059
2060 /**
2061 * List of generic function type parameter names.
2062 * Used for display, not for searching.
2063 * @type {string[]}
2064 */
2065 let lastParamNames = [];
2066
2067 // This object should have exactly the same set of fields as the "row"
2068 // object defined below. Your JavaScript runtime will thank you.
2069 // https://mathiasbynens.be/notes/shapes-ics
2070 let normalizedName = crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, "");
2071 const crateRow = {
2072 crate,
2073 ty: 3, // == ExternCrate
2074 name: crate,
2075 path: "",
2076 descShard,
2077 descIndex,
2078 exactPath: "",
2079 desc: crateCorpus.doc,
2080 parent: undefined,
2081 type: null,
2082 paramNames: lastParamNames,
2083 id,
2084 word: crate,
2085 normalizedName,
2086 bitIndex: 0,
2087 implDisambiguator: null,
2088 };
2089 this.nameTrie.insert(normalizedName, id, this.tailTable);
2090 id += 1;
2091 searchIndex.push(crateRow);
2092 currentIndex += 1;
2093 // it's not undefined
2094 // @ts-expect-error
2095 if (!this.searchIndexEmptyDesc.get(crate).contains(0)) {
2096 descIndex += 1;
2097 }
2098
2099 // see `RawSearchIndexCrate` in `rustdoc.d.ts` for a more
2100 // up to date description of these fields
2101 const itemTypes = crateCorpus.t;
2102 // an array of (String) item names
2103 const itemNames = crateCorpus.n;
2104 // an array of [(Number) item index,
2105 // (String) full path]
2106 // an item whose index is not present will fall back to the previous present path
2107 // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present,
2108 // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11
2109 const itemPaths = new Map(crateCorpus.q);
2110 // An array of [(Number) item index, (Number) path index]
2111 // Used to de-duplicate inlined and re-exported stuff
2112 const itemReexports = new Map(crateCorpus.r);
2113 // an array of (Number) the parent path index + 1 to `paths`, or 0 if none
2114 const itemParentIdxDecoder = new VlqHexDecoder(crateCorpus.i, noop => noop);
2115 // a map Number, string for impl disambiguators
2116 const implDisambiguator = new Map(crateCorpus.b);
2117 const rawPaths = crateCorpus.p;
2118 const aliases = crateCorpus.a;
2119 // an array of [(Number) item index,
2120 // (String) comma-separated list of function generic param names]
2121 // an item whose index is not present will fall back to the previous present path
2122 const itemParamNames = new Map(crateCorpus.P);
2123
2124 /**
2125 * @type {Array<{
2126 * name: string,
2127 * ty: number,
2128 * path: string|null,
2129 * exactPath: string|null,
2130 * unboxFlag: boolean
2131 * }>}
2132 */
2133 const lowercasePaths = [];
2134 /**
2135 * @type {Array<{
2136 * name: string,
2137 * ty: number,
2138 * path: string|null,
2139 * exactPath: string|null,
2140 * unboxFlag: boolean
2141 * }>}
2142 */
2143 const paths = [];
2144
2145 // a string representing the list of function types
2146 const itemFunctionDecoder = new VlqHexDecoder(
2147 crateCorpus.f,
2148 // @ts-expect-error
2149 buildFunctionSearchTypeCallback(paths, lowercasePaths),
2150 );
2151
2152 // convert `rawPaths` entries into object form
2153 // generate normalizedPaths for function search mode
2154 let len = rawPaths.length;
2155 let lastPath = undef2null(itemPaths.get(0));
2156 for (let i = 0; i < len; ++i) {
2157 const elem = rawPaths[i];
2158 const ty = elem[0];
2159 const name = elem[1];
2160 /**
2161 * @param {2|3} idx
2162 * @param {string|null} if_null
2163 * @param {string|null} if_not_found
2164 * @returns {string|null}
2165 */
2166 const elemPath = (idx, if_null, if_not_found) => {
2167 if (elem.length > idx && elem[idx] !== undefined) {
2168 const p = itemPaths.get(elem[idx]);
2169 if (p !== undefined) {
2170 return p;
2171 }
2172 return if_not_found;
2173 }
2174 return if_null;
2175 };
2176 const path = elemPath(2, lastPath, null);
2177 const exactPath = elemPath(3, path, path);
2178 const unboxFlag = elem.length > 4 && !!elem[4];
2179
2180 lowercasePaths.push({ ty, name: name.toLowerCase(), path, exactPath, unboxFlag });
2181 paths[i] = { ty, name, path, exactPath, unboxFlag };
2182 }
2183
2184 // Convert `item*` into an object form, and construct word indices.
2185 //
2186 // Before any analysis is performed, let's gather the search terms to
2187 // search against apart from the rest of the data. This is a quick
2188 // operation that is cached for the life of the page state so that
2189 // all other search operations have access to this cached data for
2190 // faster analysis operations
2191 lastPath = "";
2192 len = itemTypes.length;
2193 let lastName = "";
2194 let lastWord = "";
2195 for (let i = 0; i < len; ++i) {
2196 const bitIndex = i + 1;
2197 if (descIndex >= descShard.len &&
2198 // @ts-expect-error
2199 !this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) {
2200 descShard = {
2201 crate,
2202 shard: descShard.shard + 1,
2203 start: descShard.start + descShard.len,
2204 len: itemDescShardDecoder.next(),
2205 promise: null,
2206 resolve: null,
2207 };
2208 descIndex = 0;
2209 descShardList.push(descShard);
2210 }
2211 const name = itemNames[i] === "" ? lastName : itemNames[i];
2212 const word = itemNames[i] === "" ? lastWord : itemNames[i].toLowerCase();
2213 const pathU = itemPaths.get(i);
2214 const path = pathU !== undefined ? pathU : lastPath;
2215 const paramNameString = itemParamNames.get(i);
2216 const paramNames = paramNameString !== undefined ?
2217 paramNameString.split(",") :
2218 lastParamNames;
2219 const type = itemFunctionDecoder.next();
2220 if (type !== null) {
2221 if (type) {
2222 const fp = this.functionTypeFingerprint.subarray(id * 4, (id + 1) * 4);
2223 for (const t of type.inputs) {
2224 this.buildFunctionTypeFingerprint(t, fp);
2225 }
2226 for (const t of type.output) {
2227 this.buildFunctionTypeFingerprint(t, fp);
2228 }
2229 for (const w of type.where_clause) {
2230 for (const t of w) {
2231 this.buildFunctionTypeFingerprint(t, fp);
2232 }
2233 }
2234 }
2235 }
2236 // This object should have exactly the same set of fields as the "crateRow"
2237 // object defined above.
2238 const itemParentIdx = itemParentIdxDecoder.next();
2239 normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
2240 /** @type {rustdoc.Row} */
2241 const row = {
2242 crate,
2243 ty: itemTypes.charCodeAt(i) - 65, // 65 = "A"
2244 name,
2245 path,
2246 descShard,
2247 descIndex,
2248 exactPath: itemReexports.has(i) ?
2249 // @ts-expect-error
2250 itemPaths.get(itemReexports.get(i)) : path,
2251 // @ts-expect-error
2252 parent: itemParentIdx > 0 ? paths[itemParentIdx - 1] : undefined,
2253 type,
2254 paramNames,
2255 id,
2256 word,
2257 normalizedName,
2258 bitIndex,
2259 implDisambiguator: undef2null(implDisambiguator.get(i)),
2260 };
2261 this.nameTrie.insert(normalizedName, id, this.tailTable);
2262 id += 1;
2263 searchIndex.push(row);
2264 lastPath = row.path;
2265 lastParamNames = row.paramNames;
2266 // @ts-expect-error
2267 if (!this.searchIndexEmptyDesc.get(crate).contains(bitIndex)) {
2268 descIndex += 1;
2269 }
2270 lastName = name;
2271 lastWord = word;
2272 }
2273
2274 if (aliases) {
2275 // We need to add the aliases in `searchIndex` after we finished filling it
2276 // to not mess up indexes.
2277 allAliases.push([crate, aliases, currentIndex]);
2278 }
2279 currentIndex += itemTypes.length;
2280 this.searchState.descShards.set(crate, descShardList);
2281 }
2282
2283 for (const [crate, aliases, index] of allAliases) {
2284 for (const [alias_name, alias_refs] of Object.entries(aliases)) {
2285 if (!this.ALIASES.has(crate)) {
2286 this.ALIASES.set(crate, new Map());
2287 }
2288 const word = alias_name.toLowerCase();
2289 const crate_alias_map = this.ALIASES.get(crate);
2290 if (!crate_alias_map.has(word)) {
2291 crate_alias_map.set(word, []);
2292 }
2293 const aliases_map = crate_alias_map.get(word);
2294
2295 const normalizedName = word.indexOf("_") === -1 ? word : word.replace(/_/g, "");
2296 for (const alias of alias_refs) {
2297 const originalIndex = alias + index;
2298 const original = searchIndex[originalIndex];
2299 /** @type {rustdoc.Row} */
2300 const row = {
2301 crate,
2302 name: alias_name,
2303 normalizedName,
2304 is_alias: true,
2305 ty: original.ty,
2306 type: original.type,
2307 paramNames: [],
2308 word,
2309 id,
2310 parent: undefined,
2311 original,
2312 path: "",
2313 implDisambiguator: original.implDisambiguator,
2314 // Needed to load the description of the original item.
2315 // @ts-ignore
2316 descShard: original.descShard,
2317 descIndex: original.descIndex,
2318 bitIndex: original.bitIndex,
2319 };
2320 aliases_map.push(row);
2321 this.nameTrie.insert(normalizedName, id, this.tailTable);
2322 id += 1;
2323 searchIndex.push(row);
2324 }
2325 }
2326 }
2327 // Drop the (rather large) hash table used for reusing function items
2328 this.TYPES_POOL = new Map();
2329 return searchIndex;
2330 }
2331
2332 /**
2333 * Parses the query.
2334 *
2335 * The supported syntax by this parser is given in the rustdoc book chapter
2336 * /src/doc/rustdoc/src/read-documentation/search.md
2337 *
2338 * When adding new things to the parser, add them there, too!
2339 *
2340 * @param {string} userQuery - The user query
2341 *
2342 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} - The parsed query
2343 */
2344 static parseQuery(userQuery) {
2345 /**
2346 * @param {string} typename
2347 * @returns {number}
2348 */
2349 function itemTypeFromName(typename) {
2350 const index = itemTypes.findIndex(i => i === typename);
2351 if (index < 0) {
2352 throw ["Unknown type filter ", typename];
2353 }
2354 return index;
2355 }
2356
2357 /**
2358 * @param {rustdoc.ParserQueryElement} elem
2359 */
2360 function convertTypeFilterOnElem(elem) {
2361 if (typeof elem.typeFilter === "string") {
2362 let typeFilter = elem.typeFilter;
2363 if (typeFilter === "const") {
2364 typeFilter = "constant";
2365 }
2366 elem.typeFilter = itemTypeFromName(typeFilter);
2367 } else {
2368 elem.typeFilter = NO_TYPE_FILTER;
2369 }
2370 for (const elem2 of elem.generics) {
2371 convertTypeFilterOnElem(elem2);
2372 }
2373 for (const constraints of elem.bindings.values()) {
2374 for (const constraint of constraints) {
2375 convertTypeFilterOnElem(constraint);
2376 }
2377 }
2378 }
2379
2380 /**
2381 * Takes the user search input and returns an empty `ParsedQuery`.
2382 *
2383 * @param {string} userQuery
2384 *
2385 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}
1291 * Takes the user search input and returns an empty `ParsedQuery`.
1292 *
1293 * @param {string} userQuery
1294 *
1295 * @return {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>}
23861296 */
23871297 function newParsedQuery(userQuery) {
23881298 return {
......@@ -2437,8 +1347,7 @@ class DocSearch {
24371347 continue;
24381348 }
24391349 if (!foundStopChar) {
2440 /** @type String[] */
2441 let extra = [];
1350 let extra = EMPTY_STRING_ARRAY;
24421351 if (isLastElemGeneric(query.elems, parserState)) {
24431352 extra = [" after ", ">"];
24441353 } else if (prevIs(parserState, "\"")) {
......@@ -2515,236 +1424,623 @@ class DocSearch {
25151424
25161425 try {
25171426 parseInput(query, parserState);
1427
1428 // Scan for invalid type filters, so that we can report the error
1429 // outside the search loop.
1430 /** @param {rustdoc.ParserQueryElement} elem */
1431 const checkTypeFilter = elem => {
1432 const ty = itemTypeFromName(elem.typeFilter);
1433 if (ty === TY_GENERIC && elem.generics.length !== 0) {
1434 throw [
1435 "Generic type parameter ",
1436 elem.name,
1437 " does not accept generic parameters",
1438 ];
1439 }
1440 for (const generic of elem.generics) {
1441 checkTypeFilter(generic);
1442 }
1443 for (const constraints of elem.bindings.values()) {
1444 for (const constraint of constraints) {
1445 checkTypeFilter(constraint);
1446 }
1447 }
1448 };
25181449 for (const elem of query.elems) {
2519 convertTypeFilterOnElem(elem);
1450 checkTypeFilter(elem);
1451 }
1452 for (const elem of query.returned) {
1453 checkTypeFilter(elem);
1454 }
1455 } catch (err) {
1456 query = newParsedQuery(userQuery);
1457 if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {
1458 query.error = err;
1459 } else {
1460 // rethrow the error if it isn't a string array
1461 throw err;
1462 }
1463
1464 return query;
1465 }
1466 if (!query.literalSearch) {
1467 // If there is more than one element in the query, we switch to literalSearch in any
1468 // case.
1469 query.literalSearch = parserState.totalElems > 1;
1470 }
1471 query.foundElems = query.elems.length + query.returned.length;
1472 query.totalElems = parserState.totalElems;
1473 return query;
1474 }
1475
1476 /**
1477 * @param {number} id
1478 * @returns {Promise<string|null>}
1479 */
1480 async getName(id) {
1481 const ni = this.database.getData("name");
1482 if (!ni) {
1483 return null;
1484 }
1485 const name = await ni.at(id);
1486 return name === undefined || name === null ? null : this.utf8decoder.decode(name);
1487 }
1488
1489 /**
1490 * @param {number} id
1491 * @returns {Promise<string|null>}
1492 */
1493 async getDesc(id) {
1494 const di = this.database.getData("desc");
1495 if (!di) {
1496 return null;
1497 }
1498 const desc = await di.at(id);
1499 return desc === undefined || desc === null ? null : this.utf8decoder.decode(desc);
1500 }
1501
1502 /**
1503 * @param {number} id
1504 * @returns {Promise<number|null>}
1505 */
1506 async getAliasTarget(id) {
1507 const ai = this.database.getData("alias");
1508 if (!ai) {
1509 return null;
1510 }
1511 const bytes = await ai.at(id);
1512 if (bytes === undefined || bytes === null || bytes.length === 0) {
1513 return null;
1514 } else {
1515 /** @type {string} */
1516 const encoded = this.utf8decoder.decode(bytes);
1517 /** @type {number|null} */
1518 const decoded = JSON.parse(encoded);
1519 return decoded;
1520 }
1521 }
1522
1523 /**
1524 * @param {number} id
1525 * @returns {Promise<rustdoc.EntryData|null>}
1526 */
1527 async getEntryData(id) {
1528 const ei = this.database.getData("entry");
1529 if (!ei) {
1530 return null;
1531 }
1532 const encoded = this.utf8decoder.decode(await ei.at(id));
1533 if (encoded === "" || encoded === undefined || encoded === null) {
1534 return null;
1535 }
1536 /**
1537 * krate,
1538 * ty,
1539 * module_path,
1540 * exact_module_path,
1541 * parent,
1542 * deprecated,
1543 * associated_item_disambiguator
1544 * @type {rustdoc.ArrayWithOptionals<[
1545 * number,
1546 * rustdoc.ItemType,
1547 * number,
1548 * number,
1549 * number,
1550 * number,
1551 * ], [string]>}
1552 */
1553 const raw = JSON.parse(encoded);
1554 return {
1555 krate: raw[0],
1556 ty: raw[1],
1557 modulePath: raw[2] === 0 ? null : raw[2] - 1,
1558 exactModulePath: raw[3] === 0 ? null : raw[3] - 1,
1559 parent: raw[4] === 0 ? null : raw[4] - 1,
1560 deprecated: raw[5] === 1 ? true : false,
1561 associatedItemDisambiguator: raw.length === 6 ? null : raw[6],
1562 };
1563 }
1564
1565 /**
1566 * @param {number} id
1567 * @returns {Promise<rustdoc.PathData|null>}
1568 */
1569 async getPathData(id) {
1570 const pi = this.database.getData("path");
1571 if (!pi) {
1572 return null;
1573 }
1574 const encoded = this.utf8decoder.decode(await pi.at(id));
1575 if (encoded === "" || encoded === undefined || encoded === null) {
1576 return null;
1577 }
1578 /**
1579 * ty, module_path, exact_module_path, search_unbox, inverted_function_signature_index
1580 * @type {rustdoc.ArrayWithOptionals<[rustdoc.ItemType, string], [string|0, 0|1, string]>}
1581 */
1582 const raw = JSON.parse(encoded);
1583 return {
1584 ty: raw[0],
1585 modulePath: raw[1],
1586 exactModulePath: raw[2] === 0 || raw[2] === undefined ? raw[1] : raw[2],
1587 };
1588 }
1589
1590 /**
1591 * @param {number} id
1592 * @returns {Promise<rustdoc.FunctionData|null>}
1593 */
1594 async getFunctionData(id) {
1595 const fi = this.database.getData("function");
1596 if (!fi) {
1597 return null;
1598 }
1599 const encoded = this.utf8decoder.decode(await fi.at(id));
1600 if (encoded === "" || encoded === undefined || encoded === null) {
1601 return null;
1602 }
1603 /**
1604 * function_signature, param_names
1605 * @type {[string, string[]]}
1606 */
1607 const raw = JSON.parse(encoded);
1608
1609 const parser = new VlqHexDecoder(raw[0], async functionSearchType => {
1610 if (typeof functionSearchType === "number") {
1611 return null;
1612 }
1613 const INPUTS_DATA = 0;
1614 const OUTPUT_DATA = 1;
1615 /** @type {Promise<rustdoc.FunctionType[]>} */
1616 let inputs_;
1617 /** @type {Promise<rustdoc.FunctionType[]>} */
1618 let output_;
1619 if (typeof functionSearchType[INPUTS_DATA] === "number") {
1620 inputs_ = Promise.all([
1621 this.buildItemSearchType(functionSearchType[INPUTS_DATA]),
1622 ]);
1623 } else {
1624 // @ts-ignore
1625 inputs_ = this.buildItemSearchTypeAll(functionSearchType[INPUTS_DATA]);
1626 }
1627 if (functionSearchType.length > 1) {
1628 if (typeof functionSearchType[OUTPUT_DATA] === "number") {
1629 output_ = Promise.all([
1630 this.buildItemSearchType(functionSearchType[OUTPUT_DATA]),
1631 ]);
1632 } else {
1633 // @ts-expect-error
1634 output_ = this.buildItemSearchTypeAll(functionSearchType[OUTPUT_DATA]);
1635 }
1636 } else {
1637 output_ = Promise.resolve(EMPTY_GENERICS_ARRAY);
1638 }
1639 /** @type {Promise<rustdoc.FunctionType[]>[]} */
1640 const where_clause_ = [];
1641 const l = functionSearchType.length;
1642 for (let i = 2; i < l; ++i) {
1643 where_clause_.push(typeof functionSearchType[i] === "number"
1644 // @ts-expect-error
1645 ? Promise.all([this.buildItemSearchType(functionSearchType[i])])
1646 // @ts-expect-error
1647 : this.buildItemSearchTypeAll(functionSearchType[i]),
1648 );
1649 }
1650 const [inputs, output, where_clause] = await Promise.all([
1651 inputs_,
1652 output_,
1653 Promise.all(where_clause_),
1654 ]);
1655 return {
1656 inputs, output, where_clause,
1657 };
1658 });
1659
1660 return {
1661 functionSignature: await parser.next(),
1662 paramNames: raw[1],
1663 elemCount: parser.elemCount,
1664 };
1665 }
1666
1667 /**
1668 * @param {number} id
1669 * @returns {Promise<rustdoc.TypeData|null>}
1670 */
1671 async getTypeData(id) {
1672 const ti = this.database.getData("type");
1673 if (!ti) {
1674 return null;
1675 }
1676 const encoded = this.utf8decoder.decode(await ti.at(id));
1677 if (encoded === "" || encoded === undefined || encoded === null) {
1678 return null;
1679 }
1680 /**
1681 * function_signature, param_names
1682 * @type {[string, number] | [number] | [string] | [] | null}
1683 */
1684 const raw = JSON.parse(encoded);
1685
1686 if (!raw || raw.length === 0) {
1687 return null;
1688 }
1689
1690 let searchUnbox = false;
1691 const invertedFunctionSignatureIndex = [];
1692
1693 if (typeof raw[0] === "string") {
1694 if (raw[1]) {
1695 searchUnbox = true;
1696 }
1697 // the inverted function signature index is a list of bitmaps,
1698 // by number of types that appear in the function
1699 let i = 0;
1700 const pb = makeUint8ArrayFromBase64(raw[0]);
1701 const l = pb.length;
1702 while (i < l) {
1703 if (pb[i] === 0) {
1704 invertedFunctionSignatureIndex.push(RoaringBitmap.empty());
1705 i += 1;
1706 } else {
1707 const bitmap = new RoaringBitmap(pb, i);
1708 i += bitmap.consumed_len_bytes;
1709 invertedFunctionSignatureIndex.push(bitmap);
1710 }
1711 }
1712 } else if (raw[0]) {
1713 searchUnbox = true;
1714 }
1715
1716 return { searchUnbox, invertedFunctionSignatureIndex };
1717 }
1718
1719 /**
1720 * @returns {Promise<string[]>}
1721 */
1722 async getCrateNameList() {
1723 const crateNames = this.database.getData("crateNames");
1724 if (!crateNames) {
1725 return [];
1726 }
1727 const l = crateNames.length;
1728 const names = [];
1729 for (let i = 0; i < l; ++i) {
1730 names.push(crateNames.at(i).then(name => {
1731 if (name === undefined) {
1732 return "";
1733 }
1734 return this.utf8decoder.decode(name);
1735 }));
1736 }
1737 return Promise.all(names);
1738 }
1739
1740 /**
1741 * @param {number} id non-negative generic index
1742 * @returns {Promise<stringdex.RoaringBitmap[]>}
1743 */
1744 async getGenericInvertedIndex(id) {
1745 const gii = this.database.getData("generic_inverted_index");
1746 if (!gii) {
1747 return [];
1748 }
1749 const pb = await gii.at(id);
1750 if (pb === undefined || pb === null || pb.length === 0) {
1751 return [];
1752 }
1753
1754 const invertedFunctionSignatureIndex = [];
1755 // the inverted function signature index is a list of bitmaps,
1756 // by number of types that appear in the function
1757 let i = 0;
1758 const l = pb.length;
1759 while (i < l) {
1760 if (pb[i] === 0) {
1761 invertedFunctionSignatureIndex.push(RoaringBitmap.empty());
1762 i += 1;
1763 } else {
1764 const bitmap = new RoaringBitmap(pb, i);
1765 i += bitmap.consumed_len_bytes;
1766 invertedFunctionSignatureIndex.push(bitmap);
1767 }
1768 }
1769 return invertedFunctionSignatureIndex;
1770 }
1771
1772 /**
1773 * @param {number} id
1774 * @returns {Promise<rustdoc.Row?>}
1775 */
1776 async getRow(id) {
1777 const [name_, entry, path, type] = await Promise.all([
1778 this.getName(id),
1779 this.getEntryData(id),
1780 this.getPathData(id),
1781 this.getFunctionData(id),
1782 ]);
1783 if (!entry && !path) {
1784 return null;
1785 }
1786 const [
1787 moduleName,
1788 modulePathData,
1789 exactModuleName,
1790 exactModulePathData,
1791 ] = await Promise.all([
1792 entry && entry.modulePath !== null ? this.getName(entry.modulePath) : null,
1793 entry && entry.modulePath !== null ? this.getPathData(entry.modulePath) : null,
1794 entry && entry.exactModulePath !== null ?
1795 this.getName(entry.exactModulePath) :
1796 null,
1797 entry && entry.exactModulePath !== null ?
1798 this.getPathData(entry.exactModulePath) :
1799 null,
1800 ]);
1801 const name = name_ === null ? "" : name_;
1802 const normalizedName = (name.indexOf("_") === -1 ?
1803 name :
1804 name.replace(/_/g, "")).toLowerCase();
1805 const modulePath = modulePathData === null || moduleName === null ? "" :
1806 (modulePathData.modulePath === "" ?
1807 moduleName :
1808 `${modulePathData.modulePath}::${moduleName}`);
1809 const [parentName, parentPath] = entry !== null && entry.parent !== null ?
1810 await Promise.all([this.getName(entry.parent), this.getPathData(entry.parent)]) :
1811 [null, null];
1812 return {
1813 id,
1814 crate: entry ? nonnull(await this.getName(entry.krate)) : "",
1815 ty: entry ? entry.ty : nonnull(path).ty,
1816 name,
1817 normalizedName,
1818 modulePath,
1819 exactModulePath: exactModulePathData === null || exactModuleName === null ? modulePath :
1820 (exactModulePathData.exactModulePath === "" ?
1821 exactModuleName :
1822 `${exactModulePathData.exactModulePath}::${exactModuleName}`),
1823 entry,
1824 path,
1825 type,
1826 deprecated: entry ? entry.deprecated : false,
1827 parent: parentName !== null && parentPath !== null ?
1828 { name: parentName, path: parentPath } :
1829 null,
1830 };
1831 }
1832
1833 /**
1834 * Convert a list of RawFunctionType / ID to object-based FunctionType.
1835 *
1836 * Crates often have lots of functions in them, and it's common to have a large number of
1837 * functions that operate on a small set of data types, so the search index compresses them
1838 * by encoding function parameter and return types as indexes into an array of names.
1839 *
1840 * Even when a general-purpose compression algorithm is used, this is still a win.
1841 * I checked. https://github.com/rust-lang/rust/pull/98475#issue-1284395985
1842 *
1843 * The format for individual function types is encoded in
1844 * librustdoc/html/render/mod.rs: impl Serialize for RenderType
1845 *
1846 * @param {null|Array<rustdoc.RawFunctionType>} types
1847 *
1848 * @return {Promise<Array<rustdoc.FunctionType>>}
1849 */
1850 async buildItemSearchTypeAll(types) {
1851 return types && types.length > 0 ?
1852 await Promise.all(types.map(type => this.buildItemSearchType(type))) :
1853 EMPTY_GENERICS_ARRAY;
1854 }
1855
1856 /**
1857 * Converts a single type.
1858 *
1859 * @param {rustdoc.RawFunctionType} type
1860 * @return {Promise<rustdoc.FunctionType>}
1861 */
1862 async buildItemSearchType(type) {
1863 const PATH_INDEX_DATA = 0;
1864 const GENERICS_DATA = 1;
1865 const BINDINGS_DATA = 2;
1866 let id, generics;
1867 /**
1868 * @type {Map<number, rustdoc.FunctionType[]>}
1869 */
1870 let bindings;
1871 if (typeof type === "number") {
1872 id = type;
1873 generics = EMPTY_GENERICS_ARRAY;
1874 bindings = EMPTY_BINDINGS_MAP;
1875 } else {
1876 id = type[PATH_INDEX_DATA];
1877 generics = await this.buildItemSearchTypeAll(type[GENERICS_DATA]);
1878 if (type[BINDINGS_DATA] && type[BINDINGS_DATA].length > 0) {
1879 bindings = new Map((await Promise.all(type[BINDINGS_DATA].map(
1880 /**
1881 * @param {[rustdoc.RawFunctionType, rustdoc.RawFunctionType[]]} binding
1882 * @returns {Promise<[number, rustdoc.FunctionType[]][]>}
1883 */
1884 async binding => {
1885 const [assocType, constraints] = binding;
1886 // Associated type constructors are represented sloppily in rustdoc's
1887 // type search, to make the engine simpler.
1888 //
1889 // MyType<Output<T>=Result<T>> is equivalent to MyType<Output<Result<T>>=T>
1890 // and both are, essentially
1891 // MyType<Output=(T, Result<T>)>, except the tuple isn't actually there.
1892 // It's more like the value of a type binding is naturally an array,
1893 // which rustdoc calls "constraints".
1894 //
1895 // As a result, the key should never have generics on it.
1896 const [k, v] = await Promise.all([
1897 this.buildItemSearchType(assocType).then(t => t.id),
1898 this.buildItemSearchTypeAll(constraints),
1899 ]);
1900 return k === null ? EMPTY_BINDINGS_ARRAY : [[k, v]];
1901 },
1902 ))).flat());
1903 } else {
1904 bindings = EMPTY_BINDINGS_MAP;
1905 }
1906 }
1907 /**
1908 * @type {rustdoc.FunctionType}
1909 */
1910 let result;
1911 if (id < 0) {
1912 // types less than 0 are generic parameters
1913 // the actual names of generic parameters aren't stored, since they aren't API
1914 result = {
1915 id,
1916 name: "",
1917 ty: TY_GENERIC,
1918 path: null,
1919 exactPath: null,
1920 generics,
1921 bindings,
1922 unboxFlag: true,
1923 };
1924 } else if (id === 0) {
1925 // `0` is used as a sentinel because it's fewer bytes than `null`
1926 result = {
1927 id: null,
1928 name: "",
1929 ty: TY_GENERIC,
1930 path: null,
1931 exactPath: null,
1932 generics,
1933 bindings,
1934 unboxFlag: true,
1935 };
1936 } else {
1937 const [name, path, type] = await Promise.all([
1938 this.getName(id - 1),
1939 this.getPathData(id - 1),
1940 this.getTypeData(id - 1),
1941 ]);
1942 if (path === undefined || path === null || type === undefined || type === null) {
1943 return {
1944 id: null,
1945 name: "",
1946 ty: TY_GENERIC,
1947 path: null,
1948 exactPath: null,
1949 generics,
1950 bindings,
1951 unboxFlag: true,
1952 };
1953 }
1954 result = {
1955 id: id - 1,
1956 name,
1957 ty: path.ty,
1958 path: path.modulePath,
1959 exactPath: path.exactModulePath === null ? path.modulePath : path.exactModulePath,
1960 generics,
1961 bindings,
1962 unboxFlag: type.searchUnbox,
1963 };
1964 }
1965 const cr = this.TYPES_POOL.get(result.id);
1966 if (cr) {
1967 // Shallow equality check. Since this function is used
1968 // to construct every type object, this should be mostly
1969 // equivalent to a deep equality check, except if there's
1970 // a conflict, we don't keep the old one around, so it's
1971 // not a fully precise implementation of hashcons.
1972 if (cr.generics.length === result.generics.length &&
1973 cr.generics !== result.generics &&
1974 cr.generics.every((x, i) => result.generics[i] === x)
1975 ) {
1976 result.generics = cr.generics;
25201977 }
2521 for (const elem of query.returned) {
2522 convertTypeFilterOnElem(elem);
1978 if (cr.bindings.size === result.bindings.size && cr.bindings !== result.bindings) {
1979 let ok = true;
1980 for (const [k, v] of cr.bindings.entries()) {
1981 const v2 = result.bindings.get(k);
1982 if (!v2) {
1983 ok = false;
1984 break;
1985 }
1986 if (v !== v2 && v.length === v2.length && v.every((x, i) => v2[i] === x)) {
1987 result.bindings.set(k, v);
1988 } else if (v !== v2) {
1989 ok = false;
1990 break;
1991 }
1992 }
1993 if (ok) {
1994 result.bindings = cr.bindings;
1995 }
25231996 }
2524 } catch (err) {
2525 query = newParsedQuery(userQuery);
2526 if (Array.isArray(err) && err.every(elem => typeof elem === "string")) {
2527 query.error = err;
2528 } else {
2529 // rethrow the error if it isn't a string array
2530 throw err;
1997 if (cr.ty === result.ty && cr.path === result.path
1998 && cr.bindings === result.bindings && cr.generics === result.generics
1999 && cr.ty === result.ty && cr.name === result.name
2000 && cr.unboxFlag === result.unboxFlag
2001 ) {
2002 return cr;
25312003 }
2532
2533 return query;
2534 }
2535 if (!query.literalSearch) {
2536 // If there is more than one element in the query, we switch to literalSearch in any
2537 // case.
2538 query.literalSearch = parserState.totalElems > 1;
25392004 }
2540 query.foundElems = query.elems.length + query.returned.length;
2541 query.totalElems = parserState.totalElems;
2542 return query;
2005 this.TYPES_POOL.set(result.id, result);
2006 return result;
25432007 }
25442008
25452009 /**
25462010 * Executes the parsed query and builds a {ResultsTable}.
25472011 *
2548 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} origParsedQuery
2012 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} parsedQuery
25492013 * - The parsed user query
25502014 * @param {Object} filterCrates - Crate to search in if defined
25512015 * @param {string} currentCrate - Current crate, to rank results from this crate higher
25522016 *
25532017 * @return {Promise<rustdoc.ResultsTable>}
25542018 */
2555 async execQuery(origParsedQuery, filterCrates, currentCrate) {
2556 /** @type {rustdoc.Results} */
2557 const results_others = new Map(),
2558 /** @type {rustdoc.Results} */
2559 results_in_args = new Map(),
2560 /** @type {rustdoc.Results} */
2561 results_returned = new Map();
2562
2563 /** @type {rustdoc.ParsedQuery<rustdoc.QueryElement>} */
2564 // @ts-expect-error
2565 const parsedQuery = origParsedQuery;
2566
2019 async execQuery(parsedQuery, filterCrates, currentCrate) {
25672020 const queryLen =
25682021 parsedQuery.elems.reduce((acc, next) => acc + next.pathLast.length, 0) +
25692022 parsedQuery.returned.reduce((acc, next) => acc + next.pathLast.length, 0);
25702023 const maxEditDistance = Math.floor(queryLen / 3);
2571 // We reinitialize the `FOUND_ALIASES` map.
2572 this.FOUND_ALIASES.clear();
2573
2574 /**
2575 * @type {Map<string, number>}
2576 */
2577 const genericSymbols = new Map();
2578
2579 /**
2580 * Convert names to ids in parsed query elements.
2581 * This is not used for the "In Names" tab, but is used for the
2582 * "In Params", "In Returns", and "In Function Signature" tabs.
2583 *
2584 * If there is no matching item, but a close-enough match, this
2585 * function also that correction.
2586 *
2587 * See `buildTypeMapIndex` for more information.
2588 *
2589 * @param {rustdoc.QueryElement} elem
2590 * @param {boolean=} isAssocType
2591 */
2592 const convertNameToId = (elem, isAssocType) => {
2593 const loweredName = elem.pathLast.toLowerCase();
2594 if (this.typeNameIdMap.has(loweredName) &&
2595 // @ts-expect-error
2596 (isAssocType || !this.typeNameIdMap.get(loweredName).assocOnly)) {
2597 // @ts-expect-error
2598 elem.id = this.typeNameIdMap.get(loweredName).id;
2599 } else if (!parsedQuery.literalSearch) {
2600 let match = null;
2601 let matchDist = maxEditDistance + 1;
2602 let matchName = "";
2603 for (const [name, { id, assocOnly }] of this.typeNameIdMap) {
2604 const dist = Math.min(
2605 editDistance(name, loweredName, maxEditDistance),
2606 editDistance(name, elem.normalizedPathLast, maxEditDistance),
2607 );
2608 if (dist <= matchDist && dist <= maxEditDistance &&
2609 (isAssocType || !assocOnly)) {
2610 if (dist === matchDist && matchName > name) {
2611 continue;
2612 }
2613 match = id;
2614 matchDist = dist;
2615 matchName = name;
2616 }
2617 }
2618 if (match !== null) {
2619 parsedQuery.correction = matchName;
2620 }
2621 elem.id = match;
2622 }
2623 if ((elem.id === null && parsedQuery.totalElems > 1 && elem.typeFilter === -1
2624 && elem.generics.length === 0 && elem.bindings.size === 0)
2625 || elem.typeFilter === TY_GENERIC) {
2626 const id = genericSymbols.get(elem.normalizedPathLast);
2627 if (id !== undefined) {
2628 elem.id = id;
2629 } else {
2630 elem.id = -(genericSymbols.size + 1);
2631 genericSymbols.set(elem.normalizedPathLast, elem.id);
2632 }
2633 if (elem.typeFilter === -1 && elem.normalizedPathLast.length >= 3) {
2634 // Silly heuristic to catch if the user probably meant
2635 // to not write a generic parameter. We don't use it,
2636 // just bring it up.
2637 const maxPartDistance = Math.floor(elem.normalizedPathLast.length / 3);
2638 let matchDist = maxPartDistance + 1;
2639 let matchName = "";
2640 for (const name of this.typeNameIdMap.keys()) {
2641 const dist = editDistance(
2642 name,
2643 elem.normalizedPathLast,
2644 maxPartDistance,
2645 );
2646 if (dist <= matchDist && dist <= maxPartDistance) {
2647 if (dist === matchDist && matchName > name) {
2648 continue;
2649 }
2650 matchDist = dist;
2651 matchName = name;
2652 }
2653 }
2654 if (matchName !== "") {
2655 parsedQuery.proposeCorrectionFrom = elem.name;
2656 parsedQuery.proposeCorrectionTo = matchName;
2657 }
2658 }
2659 elem.typeFilter = TY_GENERIC;
2660 }
2661 if (elem.generics.length > 0 && elem.typeFilter === TY_GENERIC) {
2662 // Rust does not have HKT
2663 parsedQuery.error = [
2664 "Generic type parameter ",
2665 elem.name,
2666 " does not accept generic parameters",
2667 ];
2668 }
2669 for (const elem2 of elem.generics) {
2670 convertNameToId(elem2);
2671 }
2672 elem.bindings = new Map(Array.from(elem.bindings.entries())
2673 .map(entry => {
2674 const [name, constraints] = entry;
2675 // @ts-expect-error
2676 if (!this.typeNameIdMap.has(name)) {
2677 parsedQuery.error = [
2678 "Type parameter ",
2679 // @ts-expect-error
2680 name,
2681 " does not exist",
2682 ];
2683 return [0, []];
2684 }
2685 for (const elem2 of constraints) {
2686 convertNameToId(elem2, false);
2687 }
2688
2689 // @ts-expect-error
2690 return [this.typeNameIdMap.get(name).id, constraints];
2691 }),
2692 );
2693 };
2694
2695 for (const elem of parsedQuery.elems) {
2696 convertNameToId(elem, false);
2697 this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint);
2698 }
2699 for (const elem of parsedQuery.returned) {
2700 convertNameToId(elem, false);
2701 this.buildFunctionTypeFingerprint(elem, parsedQuery.typeFingerprint);
2702 }
2703
27042024
27052025 /**
2706 * Creates the query results.
2707 *
2708 * @param {Array<rustdoc.ResultObject>} results_in_args
2709 * @param {Array<rustdoc.ResultObject>} results_returned
2710 * @param {Array<rustdoc.ResultObject>} results_others
2711 * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} parsedQuery
2712 *
2713 * @return {rustdoc.ResultsTable}
2026 * @param {rustdoc.Row} item
2027 * @returns {[string, string, string]}
27142028 */
2715 function createQueryResults(
2716 results_in_args,
2717 results_returned,
2718 results_others,
2719 parsedQuery) {
2720 return {
2721 "in_args": results_in_args,
2722 "returned": results_returned,
2723 "others": results_others,
2724 "query": parsedQuery,
2725 };
2726 }
2727
2728 // @ts-expect-error
27292029 const buildHrefAndPath = item => {
27302030 let displayPath;
27312031 let href;
2732 if (item.is_alias) {
2733 this.FOUND_ALIASES.add(item.word);
2734 item = item.original;
2735 }
27362032 const type = itemTypes[item.ty];
27372033 const name = item.name;
2738 let path = item.path;
2739 let exactPath = item.exactPath;
2034 let path = item.modulePath;
2035 let exactPath = item.exactModulePath;
27402036
27412037 if (type === "mod") {
27422038 displayPath = path + "::";
27432039 href = this.rootPath + path.replace(/::/g, "/") + "/" +
27442040 name + "/index.html";
27452041 } else if (type === "import") {
2746 displayPath = item.path + "::";
2747 href = this.rootPath + item.path.replace(/::/g, "/") +
2042 displayPath = item.modulePath + "::";
2043 href = this.rootPath + item.modulePath.replace(/::/g, "/") +
27482044 "/index.html#reexport." + name;
27492045 } else if (type === "primitive" || type === "keyword") {
27502046 displayPath = "";
......@@ -2754,13 +2050,13 @@ class DocSearch {
27542050 } else if (type === "externcrate") {
27552051 displayPath = "";
27562052 href = this.rootPath + name + "/index.html";
2757 } else if (item.parent !== undefined) {
2053 } else if (item.parent) {
27582054 const myparent = item.parent;
27592055 let anchor = type + "." + name;
2760 const parentType = itemTypes[myparent.ty];
2056 const parentType = itemTypes[myparent.path.ty];
27612057 let pageType = parentType;
27622058 let pageName = myparent.name;
2763 exactPath = `${myparent.exactPath}::${myparent.name}`;
2059 exactPath = `${myparent.path.exactModulePath}::${myparent.name}`;
27642060
27652061 if (parentType === "primitive") {
27662062 displayPath = myparent.name + "::";
......@@ -2768,9 +2064,9 @@ class DocSearch {
27682064 } else if (type === "structfield" && parentType === "variant") {
27692065 // Structfields belonging to variants are special: the
27702066 // final path element is the enum name.
2771 const enumNameIdx = item.path.lastIndexOf("::");
2772 const enumName = item.path.substr(enumNameIdx + 2);
2773 path = item.path.substr(0, enumNameIdx);
2067 const enumNameIdx = item.modulePath.lastIndexOf("::");
2068 const enumName = item.modulePath.substr(enumNameIdx + 2);
2069 path = item.modulePath.substr(0, enumNameIdx);
27742070 displayPath = path + "::" + enumName + "::" + myparent.name + "::";
27752071 anchor = "variant." + myparent.name + ".field." + name;
27762072 pageType = "enum";
......@@ -2778,16 +2074,16 @@ class DocSearch {
27782074 } else {
27792075 displayPath = path + "::" + myparent.name + "::";
27802076 }
2781 if (item.implDisambiguator !== null) {
2782 anchor = item.implDisambiguator + "/" + anchor;
2077 if (item.entry && item.entry.associatedItemDisambiguator !== null) {
2078 anchor = item.entry.associatedItemDisambiguator + "/" + anchor;
27832079 }
27842080 href = this.rootPath + path.replace(/::/g, "/") +
27852081 "/" + pageType +
27862082 "." + pageName +
27872083 ".html#" + anchor;
27882084 } else {
2789 displayPath = item.path + "::";
2790 href = this.rootPath + item.path.replace(/::/g, "/") +
2085 displayPath = item.modulePath + "::";
2086 href = this.rootPath + item.modulePath.replace(/::/g, "/") +
27912087 "/" + type + "." + name + ".html";
27922088 }
27932089 return [displayPath, href, `${exactPath}::${name}`];
......@@ -2806,74 +2102,6 @@ class DocSearch {
28062102 return tmp;
28072103 }
28082104
2809 /**
2810 * Add extra data to result objects, and filter items that have been
2811 * marked for removal.
2812 *
2813 * @param {rustdoc.ResultObject[]} results
2814 * @param {"sig"|"elems"|"returned"|null} typeInfo
2815 * @returns {rustdoc.ResultObject[]}
2816 */
2817 const transformResults = (results, typeInfo) => {
2818 const duplicates = new Set();
2819 const out = [];
2820
2821 for (const result of results) {
2822 if (result.id !== -1) {
2823 const res = buildHrefAndPath(this.searchIndex[result.id]);
2824 // many of these properties don't strictly need to be
2825 // copied over, but copying them over satisfies tsc,
2826 // and hopefully plays nice with the shape optimization
2827 // of the browser engine.
2828 /** @type {rustdoc.ResultObject} */
2829 const obj = Object.assign({
2830 parent: result.parent,
2831 type: result.type,
2832 dist: result.dist,
2833 path_dist: result.path_dist,
2834 index: result.index,
2835 desc: result.desc,
2836 item: result.item,
2837 displayPath: pathSplitter(res[0]),
2838 fullPath: "",
2839 href: "",
2840 displayTypeSignature: null,
2841 }, this.searchIndex[result.id]);
2842
2843 // To be sure than it some items aren't considered as duplicate.
2844 obj.fullPath = res[2] + "|" + obj.ty;
2845
2846 if (duplicates.has(obj.fullPath)) {
2847 continue;
2848 }
2849
2850 // Exports are specifically not shown if the items they point at
2851 // are already in the results.
2852 if (obj.ty === TY_IMPORT && duplicates.has(res[2])) {
2853 continue;
2854 }
2855 if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
2856 continue;
2857 }
2858 duplicates.add(obj.fullPath);
2859 duplicates.add(res[2]);
2860
2861 if (typeInfo !== null) {
2862 obj.displayTypeSignature =
2863 // @ts-expect-error
2864 this.formatDisplayTypeSignature(obj, typeInfo);
2865 }
2866
2867 obj.href = res[1];
2868 out.push(obj);
2869 if (out.length >= MAX_RESULTS) {
2870 break;
2871 }
2872 }
2873 }
2874 return out;
2875 };
2876
28772105 /**
28782106 * Add extra data to result objects, and filter items that have been
28792107 * marked for removal.
......@@ -2883,9 +2111,11 @@ class DocSearch {
28832111 *
28842112 * @param {rustdoc.ResultObject} obj
28852113 * @param {"sig"|"elems"|"returned"|null} typeInfo
2114 * @param {rustdoc.QueryElement[]} elems
2115 * @param {rustdoc.QueryElement[]} returned
28862116 * @returns {Promise<rustdoc.DisplayTypeSignature>}
28872117 */
2888 this.formatDisplayTypeSignature = async(obj, typeInfo) => {
2118 const formatDisplayTypeSignature = async(obj, typeInfo, elems, returned) => {
28892119 const objType = obj.type;
28902120 if (!objType) {
28912121 return {type: [], mappedNames: new Map(), whereClause: new Map()};
......@@ -2897,13 +2127,13 @@ class DocSearch {
28972127 if (typeInfo !== "elems" && typeInfo !== "returned") {
28982128 fnInputs = unifyFunctionTypes(
28992129 objType.inputs,
2900 parsedQuery.elems,
2130 elems,
29012131 objType.where_clause,
29022132 null,
29032133 mgensScratch => {
29042134 fnOutput = unifyFunctionTypes(
29052135 objType.output,
2906 parsedQuery.returned,
2136 returned,
29072137 objType.where_clause,
29082138 mgensScratch,
29092139 mgensOut => {
......@@ -2917,10 +2147,9 @@ class DocSearch {
29172147 0,
29182148 );
29192149 } else {
2920 const arr = typeInfo === "elems" ? objType.inputs : objType.output;
29212150 const highlighted = unifyFunctionTypes(
2922 arr,
2923 parsedQuery.elems,
2151 typeInfo === "elems" ? objType.inputs : objType.output,
2152 typeInfo === "elems" ? elems : returned,
29242153 objType.where_clause,
29252154 null,
29262155 mgensOut => {
......@@ -2969,15 +2198,15 @@ class DocSearch {
29692198 }
29702199 };
29712200
2972 parsedQuery.elems.forEach(remapQuery);
2973 parsedQuery.returned.forEach(remapQuery);
2201 elems.forEach(remapQuery);
2202 returned.forEach(remapQuery);
29742203
29752204 /**
29762205 * Write text to a highlighting array.
29772206 * Index 0 is not highlighted, index 1 is highlighted,
29782207 * index 2 is not highlighted, etc.
29792208 *
2980 * @param {{name?: string, highlighted?: boolean}} fnType - input
2209 * @param {{name: string|null, highlighted?: boolean}} fnType - input
29812210 * @param {string[]} result
29822211 */
29832212 const pushText = (fnType, result) => {
......@@ -3004,8 +2233,9 @@ class DocSearch {
30042233 *
30052234 * @param {rustdoc.HighlightedFunctionType} fnType - input
30062235 * @param {string[]} result
2236 * @returns {Promise<void>}
30072237 */
3008 const writeHof = (fnType, result) => {
2238 const writeHof = async(fnType, result) => {
30092239 const hofOutput = fnType.bindings.get(this.typeNameIdOfOutput) || [];
30102240 const hofInputs = fnType.generics;
30112241 pushText(fnType, result);
......@@ -3016,7 +2246,7 @@ class DocSearch {
30162246 pushText({ name: ", ", highlighted: false }, result);
30172247 }
30182248 needsComma = true;
3019 writeFn(fnType, result);
2249 await writeFn(fnType, result);
30202250 }
30212251 pushText({
30222252 name: hofOutput.length === 0 ? ")" : ") -> ",
......@@ -3031,7 +2261,7 @@ class DocSearch {
30312261 pushText({ name: ", ", highlighted: false }, result);
30322262 }
30332263 needsComma = true;
3034 writeFn(fnType, result);
2264 await writeFn(fnType, result);
30352265 }
30362266 if (hofOutput.length > 1) {
30372267 pushText({name: ")", highlighted: false}, result);
......@@ -3044,8 +2274,9 @@ class DocSearch {
30442274 *
30452275 * @param {rustdoc.HighlightedFunctionType} fnType
30462276 * @param {string[]} result
2277 * @returns {Promise<boolean>}
30472278 */
3048 const writeSpecialPrimitive = (fnType, result) => {
2279 const writeSpecialPrimitive = async(fnType, result) => {
30492280 if (fnType.id === this.typeNameIdOfArray || fnType.id === this.typeNameIdOfSlice ||
30502281 fnType.id === this.typeNameIdOfTuple || fnType.id === this.typeNameIdOfUnit) {
30512282 const [ob, sb] =
......@@ -3054,7 +2285,7 @@ class DocSearch {
30542285 ["[", "]"] :
30552286 ["(", ")"];
30562287 pushText({ name: ob, highlighted: fnType.highlighted }, result);
3057 onEachBtwn(
2288 await onEachBtwnAsync(
30582289 fnType.generics,
30592290 nested => writeFn(nested, result),
30602291 // @ts-expect-error
......@@ -3065,11 +2296,11 @@ class DocSearch {
30652296 } else if (fnType.id === this.typeNameIdOfReference) {
30662297 pushText({ name: "&", highlighted: fnType.highlighted }, result);
30672298 let prevHighlighted = false;
3068 onEachBtwn(
2299 await onEachBtwnAsync(
30692300 fnType.generics,
3070 value => {
2301 async value => {
30712302 prevHighlighted = !!value.highlighted;
3072 writeFn(value, result);
2303 await writeFn(value, result);
30732304 },
30742305 // @ts-expect-error
30752306 value => pushText({
......@@ -3078,8 +2309,16 @@ class DocSearch {
30782309 }, result),
30792310 );
30802311 return true;
3081 } else if (fnType.id === this.typeNameIdOfFn) {
3082 writeHof(fnType, result);
2312 } else if (
2313 fnType.id === this.typeNameIdOfFn ||
2314 fnType.id === this.typeNameIdOfFnMut ||
2315 fnType.id === this.typeNameIdOfFnOnce ||
2316 fnType.id === this.typeNameIdOfFnPtr
2317 ) {
2318 await writeHof(fnType, result);
2319 return true;
2320 } else if (fnType.id === this.typeNameIdOfNever) {
2321 pushText({ name: "!", highlighted: fnType.highlighted }, result);
30832322 return true;
30842323 }
30852324 return false;
......@@ -3091,8 +2330,9 @@ class DocSearch {
30912330 *
30922331 * @param {rustdoc.HighlightedFunctionType} fnType
30932332 * @param {string[]} result
2333 * @returns {Promise<void>}
30942334 */
3095 const writeFn = (fnType, result) => {
2335 const writeFn = async(fnType, result) => {
30962336 if (fnType.id !== null && fnType.id < 0) {
30972337 if (fnParamNames[-1 - fnType.id] === "") {
30982338 // Normally, there's no need to shown an unhighlighted
......@@ -3101,7 +2341,7 @@ class DocSearch {
31012341 fnType.generics :
31022342 objType.where_clause[-1 - fnType.id];
31032343 for (const nested of generics) {
3104 writeFn(nested, result);
2344 await writeFn(nested, result);
31052345 }
31062346 return;
31072347 } else if (mgens) {
......@@ -3120,7 +2360,7 @@ class DocSearch {
31202360 }, result);
31212361 /** @type{string[]} */
31222362 const where = [];
3123 onEachBtwn(
2363 await onEachBtwnAsync(
31242364 fnType.generics,
31252365 nested => writeFn(nested, where),
31262366 // @ts-expect-error
......@@ -3131,32 +2371,61 @@ class DocSearch {
31312371 }
31322372 } else {
31332373 if (fnType.ty === TY_PRIMITIVE) {
3134 if (writeSpecialPrimitive(fnType, result)) {
2374 if (await writeSpecialPrimitive(fnType, result)) {
31352375 return;
31362376 }
31372377 } else if (fnType.ty === TY_TRAIT && (
31382378 fnType.id === this.typeNameIdOfFn ||
3139 fnType.id === this.typeNameIdOfFnMut ||
3140 fnType.id === this.typeNameIdOfFnOnce)) {
3141 writeHof(fnType, result);
2379 fnType.id === this.typeNameIdOfFnMut ||
2380 fnType.id === this.typeNameIdOfFnOnce ||
2381 fnType.id === this.typeNameIdOfFnPtr
2382 )) {
2383 await writeHof(fnType, result);
2384 return;
2385 } else if (fnType.name === "" &&
2386 fnType.bindings.size === 0 &&
2387 fnType.generics.length !== 0
2388 ) {
2389 pushText({ name: "impl ", highlighted: false }, result);
2390 if (fnType.generics.length > 1) {
2391 pushText({ name: "(", highlighted: false }, result);
2392 }
2393 await onEachBtwnAsync(
2394 fnType.generics,
2395 value => writeFn(value, result),
2396 // @ts-expect-error
2397 () => pushText({ name: ", ", highlighted: false }, result),
2398 );
2399 if (fnType.generics.length > 1) {
2400 pushText({ name: ")", highlighted: false }, result);
2401 }
31422402 return;
31432403 }
31442404 pushText(fnType, result);
31452405 let hasBindings = false;
31462406 if (fnType.bindings.size > 0) {
3147 onEachBtwn(
3148 fnType.bindings,
3149 ([key, values]) => {
3150 const name = this.assocTypeIdNameMap.get(key);
2407 await onEachBtwnAsync(
2408 await Promise.all([...fnType.bindings.entries()].map(
2409 /**
2410 * @param {[number, rustdoc.HighlightedFunctionType[]]} param0
2411 * @returns {Promise<[
2412 * string|null,
2413 * rustdoc.HighlightedFunctionType[],
2414 * ]>}
2415 */
2416 async([key, values]) => [await this.getName(key), values],
2417 )),
2418 async([name, values]) => {
31512419 // @ts-expect-error
31522420 if (values.length === 1 && values[0].id < 0 &&
31532421 // @ts-expect-error
3154 `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]) {
2422 `${fnType.name}::${name}` === fnParamNames[-1 - values[0].id]
2423 ) {
31552424 // the internal `Item=Iterator::Item` type variable should be
31562425 // shown in the where clause and name mapping output, but is
31572426 // redundant in this spot
31582427 for (const value of values) {
3159 writeFn(value, []);
2428 await writeFn(value, []);
31602429 }
31612430 return true;
31622431 }
......@@ -3169,7 +2438,7 @@ class DocSearch {
31692438 name: values.length !== 1 ? "=(" : "=",
31702439 highlighted: false,
31712440 }, result);
3172 onEachBtwn(
2441 await onEachBtwnAsync(
31732442 values || [],
31742443 value => writeFn(value, result),
31752444 // @ts-expect-error
......@@ -3186,7 +2455,7 @@ class DocSearch {
31862455 if (fnType.generics.length > 0) {
31872456 pushText({ name: hasBindings ? ", " : "<", highlighted: false }, result);
31882457 }
3189 onEachBtwn(
2458 await onEachBtwnAsync(
31902459 fnType.generics,
31912460 value => writeFn(value, result),
31922461 // @ts-expect-error
......@@ -3199,14 +2468,14 @@ class DocSearch {
31992468 };
32002469 /** @type {string[]} */
32012470 const type = [];
3202 onEachBtwn(
2471 await onEachBtwnAsync(
32032472 fnInputs,
32042473 fnType => writeFn(fnType, type),
32052474 // @ts-expect-error
32062475 () => pushText({ name: ", ", highlighted: false }, type),
32072476 );
32082477 pushText({ name: " -> ", highlighted: false }, type);
3209 onEachBtwn(
2478 await onEachBtwnAsync(
32102479 fnOutput,
32112480 fnType => writeFn(fnType, type),
32122481 // @ts-expect-error
......@@ -3217,176 +2486,252 @@ class DocSearch {
32172486 };
32182487
32192488 /**
3220 * This function takes a result map, and sorts it by various criteria, including edit
3221 * distance, substring match, and the crate it comes from.
2489 * Add extra data to result objects, and filter items that have been
2490 * marked for removal.
32222491 *
3223 * @param {rustdoc.Results} results
2492 * @param {[rustdoc.PlainResultObject, rustdoc.Row][]} results
32242493 * @param {"sig"|"elems"|"returned"|null} typeInfo
3225 * @param {string} preferredCrate
3226 * @returns {Promise<rustdoc.ResultObject[]>}
2494 * @param {Set<string>} duplicates
2495 * @returns {rustdoc.ResultObject[]}
32272496 */
3228 const sortResults = async(results, typeInfo, preferredCrate) => {
3229 const userQuery = parsedQuery.userQuery;
3230 const normalizedUserQuery = parsedQuery.userQuery.toLowerCase();
3231 const isMixedCase = normalizedUserQuery !== userQuery;
3232 const result_list = [];
3233 const isReturnTypeQuery = parsedQuery.elems.length === 0 ||
3234 typeInfo === "returned";
3235 for (const result of results.values()) {
3236 result.item = this.searchIndex[result.id];
3237 result.word = this.searchIndex[result.id].word;
3238 if (isReturnTypeQuery) {
3239 // We are doing a return-type based search, deprioritize "clone-like" results,
3240 // ie. functions that also take the queried type as an argument.
3241 const resultItemType = result.item && result.item.type;
3242 if (!resultItemType) {
2497 const transformResults = (results, typeInfo, duplicates) => {
2498 const out = [];
2499
2500 for (const [result, item] of results) {
2501 if (item.id !== -1) {
2502 const res = buildHrefAndPath(item);
2503 // many of these properties don't strictly need to be
2504 // copied over, but copying them over satisfies tsc,
2505 // and hopefully plays nice with the shape optimization
2506 // of the browser engine.
2507 /** @type {rustdoc.ResultObject} */
2508 const obj = Object.assign({
2509 parent: item.parent ? {
2510 path: item.parent.path.modulePath,
2511 exactPath: item.parent.path.exactModulePath ||
2512 item.parent.path.modulePath,
2513 name: item.parent.name,
2514 ty: item.parent.path.ty,
2515 } : undefined,
2516 type: item.type && item.type.functionSignature ?
2517 item.type.functionSignature :
2518 undefined,
2519 paramNames: item.type && item.type.paramNames ?
2520 item.type.paramNames :
2521 undefined,
2522 dist: result.dist,
2523 path_dist: result.path_dist,
2524 index: result.index,
2525 desc: this.getDesc(result.id),
2526 item,
2527 displayPath: pathSplitter(res[0]),
2528 fullPath: "",
2529 href: "",
2530 displayTypeSignature: null,
2531 }, result);
2532
2533 // To be sure than it some items aren't considered as duplicate.
2534 obj.fullPath = res[2] + "|" + obj.item.ty;
2535
2536 if (duplicates.has(obj.fullPath)) {
2537 continue;
2538 }
2539
2540 // Exports are specifically not shown if the items they point at
2541 // are already in the results.
2542 if (obj.item.ty === TY_IMPORT && duplicates.has(res[2])) {
2543 continue;
2544 }
2545 if (duplicates.has(res[2] + "|" + TY_IMPORT)) {
32432546 continue;
32442547 }
3245 const inputs = resultItemType.inputs;
3246 const where_clause = resultItemType.where_clause;
3247 if (containsTypeFromQuery(inputs, where_clause)) {
3248 result.path_dist *= 100;
3249 result.dist *= 100;
2548 duplicates.add(obj.fullPath);
2549 duplicates.add(res[2]);
2550
2551 if (typeInfo !== null) {
2552 obj.displayTypeSignature = formatDisplayTypeSignature(
2553 obj,
2554 typeInfo,
2555 result.elems,
2556 result.returned,
2557 );
2558 }
2559
2560 obj.href = res[1];
2561 out.push(obj);
2562 if (out.length >= MAX_RESULTS) {
2563 break;
32502564 }
32512565 }
3252 result_list.push(result);
32532566 }
32542567
3255 result_list.sort((aaa, bbb) => {
3256 /** @type {number} */
3257 let a;
3258 /** @type {number} */
3259 let b;
2568 return out;
2569 };
32602570
3261 // sort by exact case-sensitive match
3262 if (isMixedCase) {
3263 a = Number(aaa.item.name !== userQuery);
3264 b = Number(bbb.item.name !== userQuery);
3265 if (a !== b) {
3266 return a - b;
2571 const sortAndTransformResults =
2572 /**
2573 * @this {DocSearch}
2574 * @param {Array<rustdoc.PlainResultObject|null>} results
2575 * @param {"sig"|"elems"|"returned"|null} typeInfo
2576 * @param {string} preferredCrate
2577 * @param {Set<string>} duplicates
2578 * @returns {AsyncGenerator<rustdoc.ResultObject, number>}
2579 */
2580 async function*(results, typeInfo, preferredCrate, duplicates) {
2581 const userQuery = parsedQuery.userQuery;
2582 const normalizedUserQuery = parsedQuery.userQuery.toLowerCase();
2583 const isMixedCase = normalizedUserQuery !== userQuery;
2584 /**
2585 * @type {[rustdoc.PlainResultObject, rustdoc.Row][]}
2586 */
2587 const result_list = [];
2588 for (const result of results.values()) {
2589 if (!result) {
2590 continue;
2591 }
2592 /**
2593 * @type {rustdoc.Row?}
2594 */
2595 const item = await this.getRow(result.id);
2596 if (!item) {
2597 continue;
2598 }
2599 if (filterCrates !== null && item.crate !== filterCrates) {
2600 continue;
2601 }
2602 if (item) {
2603 result_list.push([result, item]);
2604 } else {
2605 continue;
32672606 }
32682607 }
32692608
3270 // sort by exact match with regard to the last word (mismatch goes later)
3271 a = Number(aaa.word !== normalizedUserQuery);
3272 b = Number(bbb.word !== normalizedUserQuery);
3273 if (a !== b) {
3274 return a - b;
3275 }
2609 result_list.sort(([aaa, aai], [bbb, bbi]) => {
2610 /** @type {number} */
2611 let a;
2612 /** @type {number} */
2613 let b;
2614
2615 if (typeInfo === null) {
2616 // in name based search...
2617
2618 // sort by exact case-sensitive match
2619 if (isMixedCase) {
2620 a = Number(aai.name !== userQuery);
2621 b = Number(bbi.name !== userQuery);
2622 if (a !== b) {
2623 return a - b;
2624 }
2625 }
32762626
3277 // sort by index of keyword in item name (no literal occurrence goes later)
3278 a = Number(aaa.index < 0);
3279 b = Number(bbb.index < 0);
3280 if (a !== b) {
3281 return a - b;
3282 }
2627 // sort by exact match with regard to the last word (mismatch goes later)
2628 a = Number(aai.normalizedName !== normalizedUserQuery);
2629 b = Number(bbi.normalizedName !== normalizedUserQuery);
2630 if (a !== b) {
2631 return a - b;
2632 }
32832633
3284 // in type based search, put functions first
3285 if (parsedQuery.hasReturnArrow) {
3286 a = Number(!isFnLikeTy(aaa.item.ty));
3287 b = Number(!isFnLikeTy(bbb.item.ty));
2634 // sort by index of keyword in item name (no literal occurrence goes later)
2635 a = Number(aaa.index < 0);
2636 b = Number(bbb.index < 0);
2637 if (a !== b) {
2638 return a - b;
2639 }
2640 }
2641
2642 // Sort by distance in the path part, if specified
2643 // (less changes required to match means higher rankings)
2644 a = Number(aaa.path_dist);
2645 b = Number(bbb.path_dist);
32882646 if (a !== b) {
32892647 return a - b;
32902648 }
3291 }
32922649
3293 // Sort by distance in the path part, if specified
3294 // (less changes required to match means higher rankings)
3295 a = Number(aaa.path_dist);
3296 b = Number(bbb.path_dist);
3297 if (a !== b) {
3298 return a - b;
3299 }
3300
3301 // (later literal occurrence, if any, goes later)
3302 a = Number(aaa.index);
3303 b = Number(bbb.index);
3304 if (a !== b) {
3305 return a - b;
3306 }
2650 // (later literal occurrence, if any, goes later)
2651 a = Number(aaa.index);
2652 b = Number(bbb.index);
2653 if (a !== b) {
2654 return a - b;
2655 }
33072656
3308 // Sort by distance in the name part, the last part of the path
3309 // (less changes required to match means higher rankings)
3310 a = Number(aaa.dist);
3311 b = Number(bbb.dist);
3312 if (a !== b) {
3313 return a - b;
3314 }
2657 // Sort by distance in the name part, the last part of the path
2658 // (less changes required to match means higher rankings)
2659 a = Number(aaa.dist);
2660 b = Number(bbb.dist);
2661 if (a !== b) {
2662 return a - b;
2663 }
33152664
3316 // sort deprecated items later
3317 a = Number(
3318 // @ts-expect-error
3319 this.searchIndexDeprecated.get(aaa.item.crate).contains(aaa.item.bitIndex),
3320 );
3321 b = Number(
3322 // @ts-expect-error
3323 this.searchIndexDeprecated.get(bbb.item.crate).contains(bbb.item.bitIndex),
3324 );
3325 if (a !== b) {
3326 return a - b;
3327 }
2665 // sort aliases lower
2666 a = Number(aaa.is_alias);
2667 b = Number(bbb.is_alias);
2668 if (a !== b) {
2669 return a - b;
2670 }
33282671
3329 // sort by crate (current crate comes first)
3330 a = Number(aaa.item.crate !== preferredCrate);
3331 b = Number(bbb.item.crate !== preferredCrate);
3332 if (a !== b) {
3333 return a - b;
3334 }
2672 // sort deprecated items later
2673 a = Number(aai.deprecated);
2674 b = Number(bbi.deprecated);
2675 if (a !== b) {
2676 return a - b;
2677 }
33352678
3336 // sort by item name length (longer goes later)
3337 a = Number(aaa.word.length);
3338 b = Number(bbb.word.length);
3339 if (a !== b) {
3340 return a - b;
3341 }
2679 // sort by crate (current crate comes first)
2680 a = Number(aai.crate !== preferredCrate);
2681 b = Number(bbi.crate !== preferredCrate);
2682 if (a !== b) {
2683 return a - b;
2684 }
33422685
3343 // sort doc alias items later
3344 a = Number(aaa.item.is_alias === true);
3345 b = Number(bbb.item.is_alias === true);
3346 if (a !== b) {
3347 return a - b;
3348 }
2686 // sort by item name length (longer goes later)
2687 a = Number(aai.normalizedName.length);
2688 b = Number(bbi.normalizedName.length);
2689 if (a !== b) {
2690 return a - b;
2691 }
33492692
3350 // sort by item name (lexicographically larger goes later)
3351 let aw = aaa.word;
3352 let bw = bbb.word;
3353 if (aw !== bw) {
3354 return (aw > bw ? +1 : -1);
3355 }
2693 // sort by item name (lexicographically larger goes later)
2694 let aw = aai.normalizedName;
2695 let bw = bbi.normalizedName;
2696 if (aw !== bw) {
2697 return (aw > bw ? +1 : -1);
2698 }
33562699
3357 // sort by description (no description goes later)
3358 a = Number(
3359 // @ts-expect-error
3360 this.searchIndexEmptyDesc.get(aaa.item.crate).contains(aaa.item.bitIndex),
3361 );
3362 b = Number(
3363 // @ts-expect-error
3364 this.searchIndexEmptyDesc.get(bbb.item.crate).contains(bbb.item.bitIndex),
3365 );
3366 if (a !== b) {
3367 return a - b;
3368 }
2700 // sort by description (no description goes later)
2701 const di = this.database.getData("desc");
2702 if (di) {
2703 a = Number(di.isEmpty(aaa.id));
2704 b = Number(di.isEmpty(bbb.id));
2705 if (a !== b) {
2706 return a - b;
2707 }
2708 }
33692709
3370 // sort by type (later occurrence in `itemTypes` goes later)
3371 a = Number(aaa.item.ty);
3372 b = Number(bbb.item.ty);
3373 if (a !== b) {
3374 return a - b;
3375 }
2710 // sort by type (later occurrence in `itemTypes` goes later)
2711 a = Number(aai.ty);
2712 b = Number(bbi.ty);
2713 if (a !== b) {
2714 return a - b;
2715 }
33762716
3377 // sort by path (lexicographically larger goes later)
3378 aw = aaa.item.path;
3379 bw = bbb.item.path;
3380 if (aw !== bw) {
3381 return (aw > bw ? +1 : -1);
3382 }
2717 // sort by path (lexicographically larger goes later)
2718 const ap = aai.modulePath;
2719 const bp = bbi.modulePath;
2720 aw = ap === undefined ? "" : ap;
2721 bw = bp === undefined ? "" : bp;
2722 if (aw !== bw) {
2723 return (aw > bw ? +1 : -1);
2724 }
33832725
3384 // que sera, sera
3385 return 0;
3386 });
2726 // que sera, sera
2727 return 0;
2728 });
33872729
3388 return transformResults(result_list, typeInfo);
3389 };
2730 const transformed_result_list = transformResults(result_list, typeInfo, duplicates);
2731 yield* transformed_result_list;
2732 return transformed_result_list.length;
2733 }
2734 .bind(this);
33902735
33912736 /**
33922737 * This function checks if a list of search query `queryElems` can all be found in the
......@@ -3938,6 +3283,8 @@ class DocSearch {
39383283 }
39393284 return true;
39403285 } else {
3286 // For these special cases, matching code need added to the inverted index.
3287 // search_index.rs -> convert_render_type does this
39413288 if (queryElem.id === this.typeNameIdOfArrayOrSlice &&
39423289 (fnType.id === this.typeNameIdOfSlice || fnType.id === this.typeNameIdOfArray)
39433290 ) {
......@@ -3948,10 +3295,12 @@ class DocSearch {
39483295 ) {
39493296 // () matches primitive:tuple or primitive:unit
39503297 // if it matches, then we're fine, and this is an appropriate match candidate
3951 } else if (queryElem.id === this.typeNameIdOfHof &&
3952 (fnType.id === this.typeNameIdOfFn || fnType.id === this.typeNameIdOfFnMut ||
3953 fnType.id === this.typeNameIdOfFnOnce)
3954 ) {
3298 } else if (queryElem.id === this.typeNameIdOfHof && (
3299 fnType.id === this.typeNameIdOfFn ||
3300 fnType.id === this.typeNameIdOfFnMut ||
3301 fnType.id === this.typeNameIdOfFnOnce ||
3302 fnType.id === this.typeNameIdOfFnPtr
3303 )) {
39553304 // -> matches fn, fnonce, and fnmut
39563305 // if it matches, then we're fine, and this is an appropriate match candidate
39573306 } else if (fnType.id !== queryElem.id || queryElem.id === null) {
......@@ -4134,21 +3483,13 @@ class DocSearch {
41343483 * This function checks if the given list contains any
41353484 * (non-generic) types mentioned in the query.
41363485 *
3486 * @param {rustdoc.QueryElement[]} elems
41373487 * @param {rustdoc.FunctionType[]} list - A list of function types.
41383488 * @param {rustdoc.FunctionType[][]} where_clause - Trait bounds for generic items.
41393489 */
4140 function containsTypeFromQuery(list, where_clause) {
3490 function containsTypeFromQuery(elems, list, where_clause) {
41413491 if (!list) return false;
4142 for (const ty of parsedQuery.returned) {
4143 // negative type ids are generics
4144 if (ty.id !== null && ty.id < 0) {
4145 continue;
4146 }
4147 if (checkIfInList(list, ty, where_clause, null, 0)) {
4148 return true;
4149 }
4150 }
4151 for (const ty of parsedQuery.elems) {
3492 for (const ty of elems) {
41523493 if (ty.id !== null && ty.id < 0) {
41533494 continue;
41543495 }
......@@ -4240,10 +3581,10 @@ class DocSearch {
42403581 /**
42413582 * Compute an "edit distance" that ignores missing path elements.
42423583 * @param {string[]} contains search query path
4243 * @param {rustdoc.Row} ty indexed item
3584 * @param {string[]} path indexed page path
42443585 * @returns {null|number} edit distance
42453586 */
4246 function checkPath(contains, ty) {
3587 function checkPath(contains, path) {
42473588 if (contains.length === 0) {
42483589 return 0;
42493590 }
......@@ -4251,11 +3592,6 @@ class DocSearch {
42513592 contains.reduce((acc, next) => acc + next.length, 0) / 3,
42523593 );
42533594 let ret_dist = maxPathEditDistance + 1;
4254 const path = ty.path.split("::");
4255
4256 if (ty.parent && ty.parent.name) {
4257 path.push(ty.parent.name.toLowerCase());
4258 }
42593595
42603596 const length = path.length;
42613597 const clength = contains.length;
......@@ -4281,7 +3617,32 @@ class DocSearch {
42813617 return ret_dist > maxPathEditDistance ? null : ret_dist;
42823618 }
42833619
4284 // @ts-expect-error
3620 /**
3621 * Compute an "edit distance" that ignores missing path elements.
3622 * @param {string[]} contains search query path
3623 * @param {rustdoc.Row} row indexed item
3624 * @returns {null|number} edit distance
3625 */
3626 function checkRowPath(contains, row) {
3627 if (contains.length === 0) {
3628 return 0;
3629 }
3630
3631 const path = row.modulePath.split("::");
3632
3633 if (row.parent && row.parent.name) {
3634 path.push(row.parent.name.toLowerCase());
3635 }
3636
3637 return checkPath(contains, path);
3638 }
3639
3640 /**
3641 *
3642 * @param {number} filter
3643 * @param {rustdoc.ItemType} type
3644 * @returns
3645 */
42853646 function typePassesFilter(filter, type) {
42863647 // No filter or Exact mach
42873648 if (filter <= NO_TYPE_FILTER || filter === type) return true;
......@@ -4303,366 +3664,839 @@ class DocSearch {
43033664 return false;
43043665 }
43053666
4306 // @ts-expect-error
4307 const handleAliases = async(ret, query, filterCrates, currentCrate) => {
4308 const lowerQuery = query.toLowerCase();
4309 if (this.FOUND_ALIASES.has(lowerQuery)) {
4310 return;
4311 }
4312 this.FOUND_ALIASES.add(lowerQuery);
4313 // We separate aliases and crate aliases because we want to have current crate
4314 // aliases to be before the others in the displayed results.
4315 // @ts-expect-error
4316 const aliases = [];
4317 // @ts-expect-error
4318 const crateAliases = [];
4319 if (filterCrates !== null) {
4320 if (this.ALIASES.has(filterCrates)
4321 && this.ALIASES.get(filterCrates).has(lowerQuery)) {
4322 const query_aliases = this.ALIASES.get(filterCrates).get(lowerQuery);
4323 for (const alias of query_aliases) {
4324 aliases.push(alias);
4325 }
3667 const innerRunNameQuery =
3668 /**
3669 * @this {DocSearch}
3670 * @param {string} currentCrate
3671 * @returns {AsyncGenerator<rustdoc.ResultObject>}
3672 */
3673 async function*(currentCrate) {
3674 const index = this.database.getIndex("normalizedName");
3675 if (!index) {
3676 return;
43263677 }
4327 } else {
4328 for (const [crate, crateAliasesIndex] of this.ALIASES) {
4329 if (crateAliasesIndex.has(lowerQuery)) {
4330 // @ts-expect-error
4331 const pushTo = crate === currentCrate ? crateAliases : aliases;
4332 const query_aliases = crateAliasesIndex.get(lowerQuery);
4333 for (const alias of query_aliases) {
4334 pushTo.push(alias);
3678 const idDuplicates = new Set();
3679 const pathDuplicates = new Set();
3680 let count = 0;
3681 const prefixResults = [];
3682 const normalizedUserQuery = parsedQuery.userQuery
3683 .replace(/[_"]/g, "")
3684 .toLowerCase();
3685 /**
3686 * @param {string} name
3687 * @param {number} alias
3688 * @param {number} dist
3689 * @param {number} index
3690 * @returns {Promise<rustdoc.PlainResultObject?>}
3691 */
3692 const handleAlias = async(name, alias, dist, index) => {
3693 return {
3694 id: alias,
3695 dist,
3696 path_dist: 0,
3697 index,
3698 alias: name,
3699 is_alias: true,
3700 elems: [], // only used in type-based queries
3701 returned: [], // only used in type-based queries
3702 original: await this.getRow(alias),
3703 };
3704 };
3705 /**
3706 * @param {Promise<rustdoc.PlainResultObject|null>[]} data
3707 * @returns {AsyncGenerator<rustdoc.ResultObject, boolean>}
3708 */
3709 const flush = async function* (data) {
3710 const satr = sortAndTransformResults(
3711 await Promise.all(data),
3712 null,
3713 currentCrate,
3714 pathDuplicates,
3715 );
3716 data.length = 0;
3717 for await (const processed of satr) {
3718 yield processed;
3719 count += 1;
3720 if ((count & 0x7F) === 0) {
3721 await yieldToBrowser();
3722 }
3723 if (count >= MAX_RESULTS) {
3724 return true;
43353725 }
43363726 }
4337 }
4338 }
4339
4340 // @ts-expect-error
4341 const sortFunc = (aaa, bbb) => {
4342 if (aaa.original.path < bbb.original.path) {
4343 return 1;
4344 } else if (aaa.original.path === bbb.original.path) {
4345 return 0;
4346 }
4347 return -1;
4348 };
4349 // @ts-expect-error
4350 crateAliases.sort(sortFunc);
4351 aliases.sort(sortFunc);
4352
4353 // @ts-expect-error
4354 const pushFunc = alias => {
4355 // Cloning `alias` to prevent its fields to be updated.
4356 alias = {...alias};
4357 const res = buildHrefAndPath(alias);
4358 alias.displayPath = pathSplitter(res[0]);
4359 alias.fullPath = alias.displayPath + alias.name;
4360 alias.href = res[1];
4361
4362 ret.others.unshift(alias);
4363 if (ret.others.length > MAX_RESULTS) {
4364 ret.others.pop();
4365 }
4366 };
4367
4368 aliases.forEach(pushFunc);
4369 // @ts-expect-error
4370 crateAliases.forEach(pushFunc);
4371 };
4372
4373 /**
4374 * This function adds the given result into the provided `results` map if it matches the
4375 * following condition:
4376 *
4377 * * If it is a "literal search" (`parsedQuery.literalSearch`), then `dist` must be 0.
4378 * * If it is not a "literal search", `dist` must be <= `maxEditDistance`.
4379 *
4380 * The `results` map contains information which will be used to sort the search results:
4381 *
4382 * * `fullId` is an `integer`` used as the key of the object we use for the `results` map.
4383 * * `id` is the index in the `searchIndex` array for this element.
4384 * * `index` is an `integer`` used to sort by the position of the word in the item's name.
4385 * * `dist` is the main metric used to sort the search results.
4386 * * `path_dist` is zero if a single-component search query is used, otherwise it's the
4387 * distance computed for everything other than the last path component.
4388 *
4389 * @param {rustdoc.Results} results
4390 * @param {number} fullId
4391 * @param {number} id
4392 * @param {number} index
4393 * @param {number} dist
4394 * @param {number} path_dist
4395 * @param {number} maxEditDistance
4396 */
4397 function addIntoResults(results, fullId, id, index, dist, path_dist, maxEditDistance) {
4398 if (dist <= maxEditDistance || index !== -1) {
4399 if (results.has(fullId)) {
4400 const result = results.get(fullId);
4401 if (result === undefined || result.dontValidate || result.dist <= dist) {
4402 return;
3727 return false;
3728 };
3729 const aliasResults = await index.search(normalizedUserQuery);
3730 if (aliasResults) {
3731 for (const id of aliasResults.matches().entries()) {
3732 const [name, alias] = await Promise.all([
3733 this.getName(id),
3734 this.getAliasTarget(id),
3735 ]);
3736 if (name !== null &&
3737 alias !== null &&
3738 !idDuplicates.has(id) &&
3739 name.replace(/[_"]/g, "").toLowerCase() === normalizedUserQuery
3740 ) {
3741 prefixResults.push(handleAlias(name, alias, 0, 0));
3742 idDuplicates.add(id);
3743 }
44033744 }
44043745 }
4405 // @ts-expect-error
4406 results.set(fullId, {
4407 id: id,
4408 index: index,
4409 dontValidate: parsedQuery.literalSearch,
4410 dist: dist,
4411 path_dist: path_dist,
4412 });
4413 }
4414 }
4415
4416 /**
4417 * This function is called in case the query has more than one element. In this case, it'll
4418 * try to match the items which validates all the elements. For `aa -> bb` will look for
4419 * functions which have a parameter `aa` and has `bb` in its returned values.
4420 *
4421 * @param {rustdoc.Row} row
4422 * @param {number} pos - Position in the `searchIndex`.
4423 * @param {rustdoc.Results} results
4424 */
4425 function handleArgs(row, pos, results) {
4426 if (!row || (filterCrates !== null && row.crate !== filterCrates)) {
4427 return;
4428 }
4429 const rowType = row.type;
4430 if (!rowType) {
4431 return;
4432 }
4433
4434 const tfpDist = compareTypeFingerprints(
4435 row.id,
4436 parsedQuery.typeFingerprint,
4437 );
4438 if (tfpDist === null) {
4439 return;
4440 }
4441 // @ts-expect-error
4442 if (results.size >= MAX_RESULTS && tfpDist > results.max_dist) {
4443 return;
4444 }
4445
4446 // If the result is too "bad", we return false and it ends this search.
4447 if (!unifyFunctionTypes(
4448 rowType.inputs,
4449 parsedQuery.elems,
4450 rowType.where_clause,
4451 null,
4452 // @ts-expect-error
4453 mgens => {
4454 return unifyFunctionTypes(
4455 rowType.output,
4456 parsedQuery.returned,
4457 rowType.where_clause,
4458 mgens,
4459 checkTypeMgensForConflict,
4460 0, // unboxing depth
4461 );
4462 },
4463 0, // unboxing depth
4464 )) {
4465 return;
4466 }
4467
4468 results.max_dist = Math.max(results.max_dist || 0, tfpDist);
4469 addIntoResults(results, row.id, pos, 0, tfpDist, 0, Number.MAX_VALUE);
4470 }
4471
4472 /**
4473 * Compare the query fingerprint with the function fingerprint.
4474 *
4475 * @param {number} fullId - The function
4476 * @param {Uint32Array} queryFingerprint - The query
4477 * @returns {number|null} - Null if non-match, number if distance
4478 * This function might return 0!
4479 */
4480 const compareTypeFingerprints = (fullId, queryFingerprint) => {
4481 const fh0 = this.functionTypeFingerprint[fullId * 4];
4482 const fh1 = this.functionTypeFingerprint[(fullId * 4) + 1];
4483 const fh2 = this.functionTypeFingerprint[(fullId * 4) + 2];
4484 const [qh0, qh1, qh2] = queryFingerprint;
4485 // Approximate set intersection with bloom filters.
4486 // This can be larger than reality, not smaller, because hashes have
4487 // the property that if they've got the same value, they hash to the
4488 // same thing. False positives exist, but not false negatives.
4489 const [in0, in1, in2] = [fh0 & qh0, fh1 & qh1, fh2 & qh2];
4490 // Approximate the set of items in the query but not the function.
4491 // This might be smaller than reality, but cannot be bigger.
4492 //
4493 // | in_ | qh_ | XOR | Meaning |
4494 // | --- | --- | --- | ------------------------------------------------ |
4495 // | 0 | 0 | 0 | Not present |
4496 // | 1 | 0 | 1 | IMPOSSIBLE because `in_` is `fh_ & qh_` |
4497 // | 1 | 1 | 0 | If one or both is false positive, false negative |
4498 // | 0 | 1 | 1 | Since in_ has no false negatives, must be real |
4499 if ((in0 ^ qh0) || (in1 ^ qh1) || (in2 ^ qh2)) {
4500 return null;
4501 }
4502 return this.functionTypeFingerprint[(fullId * 4) + 3];
4503 };
4504
4505
4506 const innerRunQuery = () => {
4507 if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) {
3746 if (parsedQuery.error !== null || parsedQuery.elems.length === 0) {
3747 yield* flush(prefixResults);
3748 return;
3749 }
45083750 const elem = parsedQuery.elems[0];
4509 // use arrow functions to preserve `this`.
4510 /** @type {function(number): void} */
4511 const handleNameSearch = id => {
4512 const row = this.searchIndex[id];
4513 if (!typePassesFilter(elem.typeFilter, row.ty) ||
3751 const typeFilter = itemTypeFromName(elem.typeFilter);
3752 /**
3753 * @param {number} id
3754 * @returns {Promise<rustdoc.PlainResultObject?>}
3755 */
3756 const handleNameSearch = async id => {
3757 const row = await this.getRow(id);
3758 if (!row || !row.entry) {
3759 return null;
3760 }
3761 if (!typePassesFilter(typeFilter, row.ty) ||
45143762 (filterCrates !== null && row.crate !== filterCrates)) {
4515 return;
3763 return null;
45163764 }
45173765
3766 /** @type {number|null} */
45183767 let pathDist = 0;
45193768 if (elem.fullPath.length > 1) {
4520
4521 const maybePathDist = checkPath(elem.pathWithoutLast, row);
4522 if (maybePathDist === null) {
4523 return;
3769 pathDist = checkRowPath(elem.pathWithoutLast, row);
3770 if (pathDist === null) {
3771 return null;
45243772 }
4525 pathDist = maybePathDist;
45263773 }
45273774
45283775 if (parsedQuery.literalSearch) {
4529 if (row.word === elem.pathLast) {
4530 addIntoResults(results_others, row.id, id, 0, 0, pathDist, 0);
4531 }
3776 return row.name.toLowerCase() === elem.pathLast ? {
3777 id,
3778 dist: 0,
3779 path_dist: 0,
3780 index: 0,
3781 elems: [], // only used in type-based queries
3782 returned: [], // only used in type-based queries
3783 is_alias: false,
3784 } : null;
45323785 } else {
4533 addIntoResults(
4534 results_others,
4535 row.id,
3786 return {
45363787 id,
4537 row.normalizedName.indexOf(elem.normalizedPathLast),
4538 editDistance(
3788 dist: editDistance(
45393789 row.normalizedName,
45403790 elem.normalizedPathLast,
45413791 maxEditDistance,
45423792 ),
4543 pathDist,
4544 maxEditDistance,
3793 path_dist: pathDist,
3794 index: row.normalizedName.indexOf(elem.normalizedPathLast),
3795 elems: [], // only used in type-based queries
3796 returned: [], // only used in type-based queries
3797 is_alias: false,
3798 };
3799 }
3800 };
3801 if (elem.normalizedPathLast === "") {
3802 // faster full-table scan for this specific case.
3803 const nameData = this.database.getData("name");
3804 const l = nameData ? nameData.length : 0;
3805 for (let id = 0; id < l; ++id) {
3806 if (!idDuplicates.has(id)) {
3807 idDuplicates.add(id);
3808 prefixResults.push(handleNameSearch(id));
3809 }
3810 if (yield* flush(prefixResults)) {
3811 return;
3812 }
3813 }
3814 return;
3815 }
3816 const results = await index.search(elem.normalizedPathLast);
3817 if (results) {
3818 for await (const result of results.prefixMatches()) {
3819 for (const id of result.entries()) {
3820 if (!idDuplicates.has(id)) {
3821 idDuplicates.add(id);
3822 prefixResults.push(handleNameSearch(id));
3823 const [name, alias] = await Promise.all([
3824 this.getName(id),
3825 this.getAliasTarget(id),
3826 ]);
3827 if (name !== null && alias !== null) {
3828 prefixResults.push(handleAlias(name, alias, 0, 0));
3829 }
3830 }
3831 }
3832 if (yield* flush(prefixResults)) {
3833 return;
3834 }
3835 }
3836 if (yield* flush(prefixResults)) {
3837 return;
3838 }
3839 }
3840 const levSearchResults = index.searchLev(elem.normalizedPathLast);
3841 const levResults = [];
3842 for await (const levResult of levSearchResults) {
3843 for (const id of levResult.matches().entries()) {
3844 if (!idDuplicates.has(id)) {
3845 idDuplicates.add(id);
3846 levResults.push(handleNameSearch(id));
3847 const [name, alias] = await Promise.all([
3848 this.getName(id),
3849 this.getAliasTarget(id),
3850 ]);
3851 if (name !== null && alias !== null) {
3852 levResults.push(handleAlias(
3853 name,
3854 alias,
3855 editDistance(elem.normalizedPathLast, name, maxEditDistance),
3856 name.indexOf(elem.normalizedPathLast),
3857 ));
3858 }
3859 }
3860 }
3861 }
3862 yield* flush(levResults);
3863 if (results) {
3864 const substringResults = [];
3865 for await (const result of results.substringMatches()) {
3866 for (const id of result.entries()) {
3867 if (!idDuplicates.has(id)) {
3868 idDuplicates.add(id);
3869 substringResults.push(handleNameSearch(id));
3870 const [name, alias] = await Promise.all([
3871 this.getName(id),
3872 this.getAliasTarget(id),
3873 ]);
3874 if (name !== null && alias !== null) {
3875 levResults.push(handleAlias(
3876 name,
3877 alias,
3878 editDistance(
3879 elem.normalizedPathLast,
3880 name,
3881 maxEditDistance,
3882 ),
3883 name.indexOf(elem.normalizedPathLast),
3884 ));
3885 }
3886 }
3887 }
3888 if (yield* flush(substringResults)) {
3889 return;
3890 }
3891 }
3892 }
3893 }
3894 .bind(this);
3895
3896 const innerRunTypeQuery =
3897 /**
3898 * @this {DocSearch}
3899 * @param {rustdoc.ParserQueryElement[]} inputs
3900 * @param {rustdoc.ParserQueryElement[]} output
3901 * @param {"sig"|"elems"|"returned"|null} typeInfo
3902 * @param {string} currentCrate
3903 * @returns {AsyncGenerator<rustdoc.ResultObject>}
3904 */
3905 async function*(inputs, output, typeInfo, currentCrate) {
3906 const index = this.database.getIndex("normalizedName");
3907 if (!index) {
3908 return;
3909 }
3910 /** @type {Map<string, number>} */
3911 const genericMap = new Map();
3912 /**
3913 * @template Q
3914 * @typedef {{
3915 * invertedIndex: stringdex.RoaringBitmap[],
3916 * queryElem: Q,
3917 * }} PostingsList
3918 */
3919 /** @type {stringdex.RoaringBitmap[]} */
3920 const empty_inverted_index = [];
3921 /** @type {PostingsList<any>[]} */
3922 const empty_postings_list = [];
3923 /** @type {stringdex.RoaringBitmap[]} */
3924 const everything_inverted_index = [];
3925 for (let i = 0; i < 64; ++i) {
3926 everything_inverted_index.push(RoaringBitmap.everything());
3927 }
3928 /**
3929 * @type {PostingsList<rustdoc.QueryElement[]>}
3930 */
3931 const everything_postings_list = {
3932 invertedIndex: everything_inverted_index,
3933 queryElem: [],
3934 };
3935 /**
3936 * @type {PostingsList<rustdoc.QueryElement[]>[]}
3937 */
3938 const nested_everything_postings_list = [everything_postings_list];
3939 /**
3940 * @param {...stringdex.RoaringBitmap[]} idx
3941 * @returns {stringdex.RoaringBitmap[]}
3942 */
3943 const intersectInvertedIndexes = (...idx) => {
3944 let i = 0;
3945 const l = idx.length;
3946 while (i < l - 1 && idx[i] === everything_inverted_index) {
3947 i += 1;
3948 }
3949 const result = [...idx[i]];
3950 for (; i < l; ++i) {
3951 if (idx[i] === everything_inverted_index) {
3952 continue;
3953 }
3954 if (idx[i].length < result.length) {
3955 result.length = idx[i].length;
3956 }
3957 for (let j = 0; j < result.length; ++j) {
3958 result[j] = result[j].intersection(idx[i][j]);
3959 }
3960 }
3961 return result;
3962 };
3963 /**
3964 * Fetch a bitmap of potentially-matching functions,
3965 * plus a list of query elements annotated with the correct IDs.
3966 *
3967 * More than one ID can exist because, for example, q=`Iter` can match
3968 * `std::vec::Iter`, or `std::btree_set::Iter`, or anything else, and those
3969 * items different IDs. What's worse, q=`Iter<Iter>` has N**2 possible
3970 * matches, because it could be `vec::Iter<btree_set::Iter>`,
3971 * `btree_set::Iter<vec::Iter>`, `vec::Iter<vec::Iter>`,
3972 * `btree_set::Iter<btree_set::Iter>`,
3973 * or anything else. This function returns all possible permutations.
3974 *
3975 * @param {rustdoc.ParserQueryElement|null} elem
3976 * @returns {Promise<PostingsList<rustdoc.QueryElement>[]>}
3977 */
3978 const unpackPostingsList = async elem => {
3979 if (!elem) {
3980 return empty_postings_list;
3981 }
3982 const typeFilter = itemTypeFromName(elem.typeFilter);
3983 const searchResults = await index.search(elem.normalizedPathLast);
3984 /**
3985 * @type {Promise<[
3986 * number,
3987 * string|null,
3988 * rustdoc.TypeData|null,
3989 * rustdoc.PathData|null,
3990 * ]>[]}
3991 * */
3992 const typePromises = [];
3993 if (typeFilter !== TY_GENERIC && searchResults) {
3994 for (const id of searchResults.matches().entries()) {
3995 typePromises.push(Promise.all([
3996 this.getName(id),
3997 this.getTypeData(id),
3998 this.getPathData(id),
3999 ]).then(([name, typeData, pathData]) =>
4000 [id, name, typeData, pathData]));
4001 }
4002 }
4003 const types = (await Promise.all(typePromises))
4004 .filter(([_id, name, ty, path]) =>
4005 name !== null && name.toLowerCase() === elem.pathLast &&
4006 ty && !ty.invertedFunctionSignatureIndex.every(bitmap => {
4007 return bitmap.isEmpty();
4008 }) &&
4009 path && path.ty !== TY_ASSOCTYPE &&
4010 (elem.pathWithoutLast.length === 0 ||
4011 checkPath(
4012 elem.pathWithoutLast,
4013 path.modulePath.split("::"),
4014 ) === 0),
4015 );
4016 if (types.length === 0) {
4017 const areGenericsAllowed = typeFilter === TY_GENERIC || (
4018 typeFilter === -1 &&
4019 (parsedQuery.totalElems > 1 || parsedQuery.hasReturnArrow) &&
4020 elem.pathWithoutLast.length === 0 &&
4021 elem.generics.length === 0 &&
4022 elem.bindings.size === 0
45454023 );
4024 if (typeFilter !== TY_GENERIC &&
4025 (elem.name.length >= 3 || !areGenericsAllowed)
4026 ) {
4027 /** @type {string|null} */
4028 let chosenName = null;
4029 /** @type {rustdoc.TypeData[]} */
4030 let chosenType = [];
4031 /** @type {rustdoc.PathData[]} */
4032 let chosenPath = [];
4033 /** @type {number[]} */
4034 let chosenId = [];
4035 let chosenDist = Number.MAX_SAFE_INTEGER;
4036 const levResults = index.searchLev(elem.normalizedPathLast);
4037 for await (const searchResults of levResults) {
4038 for (const id of searchResults.matches().entries()) {
4039 const [name, ty, path] = await Promise.all([
4040 this.getName(id),
4041 this.getTypeData(id),
4042 this.getPathData(id),
4043 ]);
4044 if (name !== null && ty !== null && path !== null &&
4045 !ty.invertedFunctionSignatureIndex.every(bitmap => {
4046 return bitmap.isEmpty();
4047 }) &&
4048 path.ty !== TY_ASSOCTYPE
4049 ) {
4050 let dist = editDistance(
4051 name,
4052 elem.pathLast,
4053 maxEditDistance,
4054 );
4055 if (elem.pathWithoutLast.length !== 0) {
4056 const pathDist = checkPath(
4057 elem.pathWithoutLast,
4058 path.modulePath.split("::"),
4059 );
4060 // guaranteed to be higher than the path limit
4061 dist += pathDist === null ?
4062 Number.MAX_SAFE_INTEGER :
4063 pathDist;
4064 }
4065 if (name === chosenName) {
4066 chosenId.push(id);
4067 chosenType.push(ty);
4068 chosenPath.push(path);
4069 } else if (dist < chosenDist) {
4070 chosenName = name;
4071 chosenId = [id];
4072 chosenType = [ty];
4073 chosenPath = [path];
4074 chosenDist = dist;
4075 }
4076 }
4077 }
4078 if (chosenId.length !== 0) {
4079 // searchLev returns results in order
4080 // if we have working matches, we're done
4081 break;
4082 }
4083 }
4084 if (areGenericsAllowed) {
4085 parsedQuery.proposeCorrectionFrom = elem.name;
4086 parsedQuery.proposeCorrectionTo = chosenName;
4087 } else {
4088 parsedQuery.correction = chosenName;
4089 for (let i = 0; i < chosenType.length; ++i) {
4090 types.push([
4091 chosenId[i],
4092 chosenName,
4093 chosenType[i],
4094 chosenPath[i],
4095 ]);
4096 }
4097 }
4098 }
4099 if (areGenericsAllowed) {
4100 let genericId = genericMap.get(elem.normalizedPathLast);
4101 if (genericId === undefined) {
4102 genericId = genericMap.size;
4103 genericMap.set(elem.normalizedPathLast, genericId);
4104 }
4105 return [{
4106 invertedIndex: await this.getGenericInvertedIndex(genericId),
4107 queryElem: {
4108 name: elem.name,
4109 id: (-genericId) - 1,
4110 typeFilter: TY_GENERIC,
4111 generics: [],
4112 bindings: EMPTY_BINDINGS_MAP,
4113 fullPath: elem.fullPath,
4114 pathLast: elem.pathLast,
4115 normalizedPathLast: elem.normalizedPathLast,
4116 pathWithoutLast: elem.pathWithoutLast,
4117 },
4118 }];
4119 }
4120 }
4121 types.sort(([_i, name1, _t, pathData1], [_i2, name2, _t2, pathData2]) => {
4122 const p1 = !pathData1 ? "" : pathData1.modulePath;
4123 const p2 = !pathData2 ? "" : pathData2.modulePath;
4124 const n1 = name1 === null ? "" : name1;
4125 const n2 = name2 === null ? "" : name2;
4126 if (p1.length !== p2.length) {
4127 return p1.length > p2.length ? +1 : -1;
4128 }
4129 if (n1.length !== n2.length) {
4130 return n1.length > n2.length ? +1 : -1;
4131 }
4132 if (n1 !== n2) {
4133 return n1 > n2 ? +1 : -1;
4134 }
4135 if (p1 !== p2) {
4136 return p1 > p2 ? +1 : -1;
4137 }
4138 return 0;
4139 });
4140 /** @type {PostingsList<rustdoc.QueryElement>[]} */
4141 const results = [];
4142 for (const [id, _name, typeData] of types) {
4143 if (!typeData || typeData.invertedFunctionSignatureIndex.every(bitmap => {
4144 return bitmap.isEmpty();
4145 })) {
4146 continue;
4147 }
4148 const upla = await unpackPostingsListAll(elem.generics);
4149 const uplb = await unpackPostingsListBindings(elem.bindings);
4150 for (const {invertedIndex: genericsIdx, queryElem: generics} of upla) {
4151 for (const {invertedIndex: bindingsIdx, queryElem: bindings} of uplb) {
4152 results.push({
4153 invertedIndex: intersectInvertedIndexes(
4154 typeData.invertedFunctionSignatureIndex,
4155 genericsIdx,
4156 bindingsIdx,
4157 ),
4158 queryElem: {
4159 name: elem.name,
4160 id,
4161 typeFilter,
4162 generics,
4163 bindings,
4164 fullPath: elem.fullPath,
4165 pathLast: elem.pathLast,
4166 normalizedPathLast: elem.normalizedPathLast,
4167 pathWithoutLast: elem.pathWithoutLast,
4168 },
4169 });
4170 if ((results.length & 0x7F) === 0) {
4171 await yieldToBrowser();
4172 }
4173 }
4174 }
4175 }
4176 return results;
4177 };
4178 /**
4179 * Fetch all possible matching permutations of a list of query elements.
4180 *
4181 * The empty list returns an "identity postings list", with a bitmap that
4182 * matches everything and an empty list of elems. This allows you to safely
4183 * take the intersection of this bitmap.
4184 *
4185 * @param {(rustdoc.ParserQueryElement|null)[]|null} elems
4186 * @returns {Promise<PostingsList<rustdoc.QueryElement[]>[]>}
4187 */
4188 const unpackPostingsListAll = async elems => {
4189 if (!elems || elems.length === 0) {
4190 return nested_everything_postings_list;
4191 }
4192 const [firstPostingsList, remainingAll] = await Promise.all([
4193 unpackPostingsList(elems[0]),
4194 unpackPostingsListAll(elems.slice(1)),
4195 ]);
4196 /** @type {PostingsList<rustdoc.QueryElement[]>[]} */
4197 const results = [];
4198 for (const {
4199 invertedIndex: firstIdx,
4200 queryElem: firstElem,
4201 } of firstPostingsList) {
4202 for (const {
4203 invertedIndex: remainingIdx,
4204 queryElem: remainingElems,
4205 } of remainingAll) {
4206 results.push({
4207 invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx),
4208 queryElem: [firstElem, ...remainingElems],
4209 });
4210 if ((results.length & 0x7F) === 0) {
4211 await yieldToBrowser();
4212 }
4213 }
4214 }
4215 return results;
4216 };
4217 /**
4218 * Fetch all possible matching permutations of a map query element bindings.
4219 *
4220 * The empty list returns an "identity postings list", with a bitmap that
4221 * matches everything and an empty list of elems. This allows you to safely
4222 * take the intersection of this bitmap.
4223 *
4224 * Heads up! This function mutates the Map that you provide.
4225 * Before passing an actual parser item to it, make sure to clone the map.
4226 *
4227 * @param {Map<string, rustdoc.ParserQueryElement[]>} elems
4228 * @returns {Promise<PostingsList<
4229 * Map<number, rustdoc.QueryElement[]>,
4230 * >[]>}
4231 */
4232 const unpackPostingsListBindings = async elems => {
4233 if (!elems) {
4234 return [{
4235 invertedIndex: everything_inverted_index,
4236 queryElem: new Map(),
4237 }];
4238 }
4239 const firstKey = elems.keys().next().value;
4240 if (firstKey === undefined) {
4241 return [{
4242 invertedIndex: everything_inverted_index,
4243 queryElem: new Map(),
4244 }];
4245 }
4246 const firstList = elems.get(firstKey);
4247 if (firstList === undefined) {
4248 return [{
4249 invertedIndex: everything_inverted_index,
4250 queryElem: new Map(),
4251 }];
4252 }
4253 const firstKeyIds = await index.search(firstKey);
4254 if (!firstKeyIds) {
4255 // User specified a non-existent key.
4256 return [{
4257 invertedIndex: empty_inverted_index,
4258 queryElem: new Map(),
4259 }];
4260 }
4261 elems.delete(firstKey);
4262 const [firstPostingsList, remainingAll] = await Promise.all([
4263 unpackPostingsListAll(firstList),
4264 unpackPostingsListBindings(elems),
4265 ]);
4266 /** @type {PostingsList<Map<number, rustdoc.QueryElement[]>>[]} */
4267 const results = [];
4268 for (const keyId of firstKeyIds.matches().entries()) {
4269 for (const {
4270 invertedIndex: firstIdx,
4271 queryElem: firstElem,
4272 } of firstPostingsList) {
4273 for (const {
4274 invertedIndex: remainingIdx,
4275 queryElem: remainingElems,
4276 } of remainingAll) {
4277 const elems = new Map(remainingElems);
4278 elems.set(keyId, firstElem);
4279 results.push({
4280 invertedIndex: intersectInvertedIndexes(firstIdx, remainingIdx),
4281 queryElem: elems,
4282 });
4283 if ((results.length & 0x7F) === 0) {
4284 await yieldToBrowser();
4285 }
4286 }
4287 }
45464288 }
4289 elems.set(firstKey, firstList);
4290 if (results.length === 0) {
4291 // User specified a non-existent key.
4292 return [{
4293 invertedIndex: empty_inverted_index,
4294 queryElem: new Map(),
4295 }];
4296 }
4297 return results;
45474298 };
4548 if (elem.normalizedPathLast !== "") {
4549 const last = elem.normalizedPathLast;
4550 for (const id of this.nameTrie.search(last, this.tailTable)) {
4551 handleNameSearch(id);
4299
4300 // finally, we can do the actual unification loop
4301 const [allInputs, allOutput] = await Promise.all([
4302 unpackPostingsListAll(inputs),
4303 unpackPostingsListAll(output),
4304 ]);
4305 let checkCounter = 0;
4306 /**
4307 * Finally, we can perform an incremental search, sorted by the number of
4308 * entries that match a given query.
4309 *
4310 * The outer list gives the number of elements. The inner one is separate
4311 * for each distinct name resolution.
4312 *
4313 * @type {{
4314 * bitmap: stringdex.RoaringBitmap,
4315 * inputs: rustdoc.QueryElement[],
4316 * output: rustdoc.QueryElement[],
4317 * }[][]}
4318 */
4319 const queryPlan = [];
4320 for (const {invertedIndex: inputsIdx, queryElem: inputs} of allInputs) {
4321 for (const {invertedIndex: outputIdx, queryElem: output} of allOutput) {
4322 const invertedIndex = intersectInvertedIndexes(inputsIdx, outputIdx);
4323 for (const [size, bitmap] of invertedIndex.entries()) {
4324 checkCounter += 1;
4325 if ((checkCounter & 0x7F) === 0) {
4326 await yieldToBrowser();
4327 }
4328 if (!queryPlan[size]) {
4329 queryPlan[size] = [];
4330 }
4331 queryPlan[size].push({
4332 bitmap, inputs, output,
4333 });
4334 }
45524335 }
45534336 }
4554 const length = this.searchIndex.length;
4555
4556 for (let i = 0, nSearchIndex = length; i < nSearchIndex; ++i) {
4557 // queries that end in :: bypass the trie
4558 if (elem.normalizedPathLast === "") {
4559 handleNameSearch(i);
4560 }
4561 const row = this.searchIndex[i];
4562 if (filterCrates !== null && row.crate !== filterCrates) {
4563 continue;
4564 }
4565 const tfpDist = compareTypeFingerprints(
4566 row.id,
4567 parsedQuery.typeFingerprint,
4568 );
4569 if (tfpDist !== null) {
4570 const in_args = row.type && row.type.inputs
4571 && checkIfInList(row.type.inputs, elem, row.type.where_clause, null, 0);
4572 const returned = row.type && row.type.output
4573 && checkIfInList(row.type.output, elem, row.type.where_clause, null, 0);
4574 if (in_args) {
4575 results_in_args.max_dist = Math.max(
4576 results_in_args.max_dist || 0,
4577 tfpDist,
4578 );
4579 const maxDist = results_in_args.size < MAX_RESULTS ?
4580 (tfpDist + 1) :
4581 results_in_args.max_dist;
4582 addIntoResults(results_in_args, row.id, i, -1, tfpDist, 0, maxDist);
4337 const resultPromises = [];
4338 const dedup = new Set();
4339 let resultCounter = 0;
4340 const isReturnTypeQuery = inputs.length === 0;
4341 /** @type {rustdoc.PlainResultObject[]} */
4342 const pushToBottom = [];
4343 plan: for (const queryStep of queryPlan) {
4344 for (const {bitmap, inputs, output} of queryStep) {
4345 for (const id of bitmap.entries()) {
4346 checkCounter += 1;
4347 if ((checkCounter & 0x7F) === 0) {
4348 await yieldToBrowser();
4349 }
4350 resultPromises.push(this.getFunctionData(id).then(async fnData => {
4351 if (!fnData || !fnData.functionSignature) {
4352 return null;
4353 }
4354 checkCounter += 1;
4355 if ((checkCounter & 0x7F) === 0) {
4356 await yieldToBrowser();
4357 }
4358 const functionSignature = fnData.functionSignature;
4359 if (!unifyFunctionTypes(
4360 functionSignature.inputs,
4361 inputs,
4362 functionSignature.where_clause,
4363 null,
4364 mgens => {
4365 return !!unifyFunctionTypes(
4366 functionSignature.output,
4367 output,
4368 functionSignature.where_clause,
4369 mgens,
4370 checkTypeMgensForConflict,
4371 0, // unboxing depth
4372 );
4373 },
4374 0, // unboxing depth
4375 )) {
4376 return null;
4377 }
4378 const result = {
4379 id,
4380 dist: fnData.elemCount,
4381 path_dist: 0,
4382 index: -1,
4383 elems: inputs,
4384 returned: output,
4385 is_alias: false,
4386 };
4387 const entry = await this.getEntryData(id);
4388 if ((entry && !isFnLikeTy(entry.ty)) ||
4389 (isReturnTypeQuery &&
4390 functionSignature &&
4391 containsTypeFromQuery(
4392 output,
4393 functionSignature.inputs,
4394 functionSignature.where_clause,
4395 )
4396 )
4397 ) {
4398 pushToBottom.push(result);
4399 return null;
4400 }
4401 return result;
4402 }));
45834403 }
4584 if (returned) {
4585 results_returned.max_dist = Math.max(
4586 results_returned.max_dist || 0,
4587 tfpDist,
4588 );
4589 const maxDist = results_returned.size < MAX_RESULTS ?
4590 (tfpDist + 1) :
4591 results_returned.max_dist;
4592 addIntoResults(results_returned, row.id, i, -1, tfpDist, 0, maxDist);
4404 }
4405 for await (const result of sortAndTransformResults(
4406 await Promise.all(resultPromises),
4407 typeInfo,
4408 currentCrate,
4409 dedup,
4410 )) {
4411 if (resultCounter >= MAX_RESULTS) {
4412 break plan;
45934413 }
4414 yield result;
4415 resultCounter += 1;
45944416 }
4417 resultPromises.length = 0;
45954418 }
4596 } else if (parsedQuery.foundElems > 0) {
4597 // Sort input and output so that generic type variables go first and
4598 // types with generic parameters go last.
4599 // That's because of the way unification is structured: it eats off
4600 // the end, and hits a fast path if the last item is a simple atom.
4601 /** @type {function(rustdoc.QueryElement, rustdoc.QueryElement): number} */
4602 const sortQ = (a, b) => {
4603 const ag = a.generics.length === 0 && a.bindings.size === 0;
4604 const bg = b.generics.length === 0 && b.bindings.size === 0;
4605 if (ag !== bg) {
4606 // unary `+` converts booleans into integers.
4607 return +ag - +bg;
4419 if (resultCounter >= MAX_RESULTS) {
4420 return;
4421 }
4422 for await (const result of sortAndTransformResults(
4423 await Promise.all(pushToBottom),
4424 typeInfo,
4425 currentCrate,
4426 dedup,
4427 )) {
4428 if (resultCounter >= MAX_RESULTS) {
4429 break;
46084430 }
4609 const ai = a.id !== null && a.id > 0;
4610 const bi = b.id !== null && b.id > 0;
4611 return +ai - +bi;
4612 };
4613 parsedQuery.elems.sort(sortQ);
4614 parsedQuery.returned.sort(sortQ);
4615 for (let i = 0, nSearchIndex = this.searchIndex.length; i < nSearchIndex; ++i) {
4616 handleArgs(this.searchIndex[i], i, results_others);
4431 yield result;
4432 resultCounter += 1;
46174433 }
46184434 }
4619 };
4620
4621 if (parsedQuery.error === null) {
4622 innerRunQuery();
4623 }
4624
4625 const isType = parsedQuery.foundElems !== 1 || parsedQuery.hasReturnArrow;
4626 const [sorted_in_args, sorted_returned, sorted_others] = await Promise.all([
4627 sortResults(results_in_args, "elems", currentCrate),
4628 sortResults(results_returned, "returned", currentCrate),
4629 // @ts-expect-error
4630 sortResults(results_others, (isType ? "query" : null), currentCrate),
4631 ]);
4632 const ret = createQueryResults(
4633 sorted_in_args,
4634 sorted_returned,
4635 sorted_others,
4636 parsedQuery);
4637 await handleAliases(ret, parsedQuery.userQuery.replace(/"/g, ""),
4638 filterCrates, currentCrate);
4639 await Promise.all([ret.others, ret.returned, ret.in_args].map(async list => {
4640 const descs = await Promise.all(list.map(result => {
4641 // @ts-expect-error
4642 return this.searchIndexEmptyDesc.get(result.crate).contains(result.bitIndex) ?
4643 "" :
4644 // @ts-expect-error
4645 this.searchState.loadDesc(result);
4646 }));
4647 for (const [i, result] of list.entries()) {
4648 // @ts-expect-error
4649 result.desc = descs[i];
4650 }
4651 }));
4652 if (parsedQuery.error !== null && ret.others.length !== 0) {
4653 // It means some doc aliases were found so let's "remove" the error!
4654 ret.query.error = null;
4435 .bind(this);
4436
4437 if (parsedQuery.foundElems === 1 && !parsedQuery.hasReturnArrow) {
4438 // We never want the main tab to delay behind the other two tabs.
4439 // This is a bit of a hack (because JS's scheduler doesn't have much of an API),
4440 // along with making innerRunTypeQuery yield to the UI thread.
4441 const {
4442 promise: donePromise,
4443 resolve: doneResolve,
4444 reject: doneReject,
4445 } = Promise.withResolvers();
4446 const doneTimeout = timeout(250);
4447 return {
4448 "in_args": (async function*() {
4449 await Promise.race([donePromise, doneTimeout]);
4450 yield* innerRunTypeQuery(parsedQuery.elems, [], "elems", currentCrate);
4451 })(),
4452 "returned": (async function*() {
4453 await Promise.race([donePromise, doneTimeout]);
4454 yield* innerRunTypeQuery([], parsedQuery.elems, "returned", currentCrate);
4455 })(),
4456 "others": (async function*() {
4457 try {
4458 yield* innerRunNameQuery(currentCrate);
4459 doneResolve(null);
4460 } catch (e) {
4461 doneReject(e);
4462 throw e;
4463 }
4464 })(),
4465 "query": parsedQuery,
4466 };
4467 } else if (parsedQuery.error !== null) {
4468 return {
4469 "in_args": (async function*() {})(),
4470 "returned": (async function*() {})(),
4471 "others": innerRunNameQuery(currentCrate),
4472 "query": parsedQuery,
4473 };
4474 } else {
4475 const typeInfo = parsedQuery.elems.length === 0 ?
4476 "returned" : (
4477 parsedQuery.returned.length === 0 ? "elems" : "sig"
4478 );
4479 return {
4480 "in_args": (async function*() {})(),
4481 "returned": (async function*() {})(),
4482 "others": parsedQuery.foundElems === 0 ?
4483 (async function*() {})() :
4484 innerRunTypeQuery(
4485 parsedQuery.elems,
4486 parsedQuery.returned,
4487 typeInfo,
4488 currentCrate,
4489 ),
4490 "query": parsedQuery,
4491 };
46554492 }
4656 return ret;
46574493 }
46584494}
46594495
46604496
46614497// ==================== Core search logic end ====================
46624498
4663/** @type {Map<string, rustdoc.RawSearchIndexCrate>} */
4664let rawSearchIndex;
4665// @ts-expect-error
4499/** @type {DocSearch} */
46664500let docSearch;
46674501const longItemTypes = [
46684502 "keyword",
......@@ -4762,12 +4596,8 @@ function buildUrl(search, filterCrates) {
47624596function getFilterCrates() {
47634597 const elem = document.getElementById("crate-search");
47644598
4765 if (elem &&
4766 // @ts-expect-error
4767 elem.value !== "all crates" &&
4768 // @ts-expect-error
4769 window.searchIndex.has(elem.value)
4770 ) {
4599 // @ts-expect-error
4600 if (elem && elem.value !== "all crates") {
47714601 // @ts-expect-error
47724602 return elem.value;
47734603 }
......@@ -4777,8 +4607,7 @@ function getFilterCrates() {
47774607// @ts-expect-error
47784608function nextTab(direction) {
47794609 const next = (searchState.currentTab + direction + 3) % searchState.focusedByTab.length;
4780 // @ts-expect-error
4781 searchState.focusedByTab[searchState.currentTab] = document.activeElement;
4610 window.searchState.focusedByTab[searchState.currentTab] = document.activeElement;
47824611 printTab(next);
47834612 focusSearchResult();
47844613}
......@@ -4790,133 +4619,182 @@ function focusSearchResult() {
47904619 document.querySelectorAll(".search-results.active a").item(0) ||
47914620 document.querySelectorAll("#search-tabs button").item(searchState.currentTab);
47924621 searchState.focusedByTab[searchState.currentTab] = null;
4793 if (target) {
4794 // @ts-expect-error
4622 if (target && target instanceof HTMLElement) {
47954623 target.focus();
47964624 }
47974625}
47984626
47994627/**
48004628 * Render a set of search results for a single tab.
4801 * @param {Array<?>} array - The search results for this tab
4802 * @param {rustdoc.ParsedQuery<rustdoc.QueryElement>} query
4629 * @param {AsyncGenerator<rustdoc.ResultObject>} results - The search results for this tab
4630 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query
48034631 * @param {boolean} display - True if this is the active tab
4632 * @param {function(number, HTMLElement): any} finishedCallback
4633 * @param {boolean} isTypeSearch
4634 * @returns {Promise<HTMLElement>}
48044635 */
4805async function addTab(array, query, display) {
4636async function addTab(results, query, display, finishedCallback, isTypeSearch) {
48064637 const extraClass = display ? " active" : "";
48074638
4808 const output = document.createElement(
4809 array.length === 0 && query.error === null ? "div" : "ul",
4810 );
4811 if (array.length > 0) {
4812 output.className = "search-results " + extraClass;
4639 /** @type {HTMLElement} */
4640 let output = document.createElement("ul");
4641 output.className = "search-results " + extraClass;
48134642
4814 const lis = Promise.all(array.map(async item => {
4815 const name = item.is_alias ? item.original.name : item.name;
4816 const type = itemTypes[item.ty];
4817 const longType = longItemTypes[item.ty];
4818 const typeName = longType.length !== 0 ? `${longType}` : "?";
4643 let count = 0;
48194644
4820 const link = document.createElement("a");
4821 link.className = "result-" + type;
4822 link.href = item.href;
4645 /** @type {Promise<string|null>[]} */
4646 const descList = [];
48234647
4824 const resultName = document.createElement("span");
4825 resultName.className = "result-name";
4648 /** @param {rustdoc.ResultObject} obj */
4649 const addNextResultToOutput = async obj => {
4650 count += 1;
48264651
4827 resultName.insertAdjacentHTML(
4828 "beforeend",
4829 `<span class="typename">${typeName}</span>`);
4830 link.appendChild(resultName);
4652 const name = obj.item.name;
4653 const type = itemTypes[obj.item.ty];
4654 const longType = longItemTypes[obj.item.ty];
4655 const typeName = longType.length !== 0 ? `${longType}` : "?";
48314656
4832 let alias = " ";
4833 if (item.is_alias) {
4834 alias = ` <div class="alias">\
4835<b>${item.name}</b><i class="grey">&nbsp;- see&nbsp;</i>\
4657 const link = document.createElement("a");
4658 link.className = "result-" + type;
4659 link.href = obj.href;
4660
4661 const resultName = document.createElement("span");
4662 resultName.className = "result-name";
4663
4664 resultName.insertAdjacentHTML(
4665 "beforeend",
4666 `<span class="typename">${typeName}</span>`);
4667 link.appendChild(resultName);
4668
4669 let alias = " ";
4670 if (obj.alias !== undefined) {
4671 alias = ` <div class="alias">\
4672<b>${obj.alias}</b><i class="grey">&nbsp;- see&nbsp;</i>\
48364673</div>`;
4837 }
4838 resultName.insertAdjacentHTML(
4839 "beforeend",
4840 `<div class="path">${alias}\
4841${item.displayPath}<span class="${type}">${name}</span>\
4674 }
4675 resultName.insertAdjacentHTML(
4676 "beforeend",
4677 `<div class="path">${alias}\
4678${obj.displayPath}<span class="${type}">${name}</span>\
48424679</div>`);
48434680
4844 const description = document.createElement("div");
4845 description.className = "desc";
4846 description.insertAdjacentHTML("beforeend", item.desc);
4847 if (item.displayTypeSignature) {
4848 const {type, mappedNames, whereClause} = await item.displayTypeSignature;
4849 const displayType = document.createElement("div");
4850 // @ts-expect-error
4851 type.forEach((value, index) => {
4852 if (index % 2 !== 0) {
4853 const highlight = document.createElement("strong");
4854 highlight.appendChild(document.createTextNode(value));
4855 displayType.appendChild(highlight);
4856 } else {
4857 displayType.appendChild(document.createTextNode(value));
4681 const description = document.createElement("div");
4682 description.className = "desc";
4683 obj.desc.then(desc => {
4684 if (desc !== null) {
4685 description.insertAdjacentHTML("beforeend", desc);
4686 }
4687 });
4688 descList.push(obj.desc);
4689 if (obj.displayTypeSignature) {
4690 const {type, mappedNames, whereClause} = await obj.displayTypeSignature;
4691 const displayType = document.createElement("div");
4692 type.forEach((value, index) => {
4693 if (index % 2 !== 0) {
4694 const highlight = document.createElement("strong");
4695 highlight.appendChild(document.createTextNode(value));
4696 displayType.appendChild(highlight);
4697 } else {
4698 displayType.appendChild(document.createTextNode(value));
4699 }
4700 });
4701 if (mappedNames.size > 0 || whereClause.size > 0) {
4702 let addWhereLineFn = () => {
4703 const line = document.createElement("div");
4704 line.className = "where";
4705 line.appendChild(document.createTextNode("where"));
4706 displayType.appendChild(line);
4707 addWhereLineFn = () => {};
4708 };
4709 for (const [qname, name] of mappedNames) {
4710 // don't care unless the generic name is different
4711 if (name === qname) {
4712 continue;
48584713 }
4859 });
4860 if (mappedNames.size > 0 || whereClause.size > 0) {
4861 let addWhereLineFn = () => {
4862 const line = document.createElement("div");
4863 line.className = "where";
4864 line.appendChild(document.createTextNode("where"));
4865 displayType.appendChild(line);
4866 addWhereLineFn = () => {};
4867 };
4868 for (const [qname, name] of mappedNames) {
4869 // don't care unless the generic name is different
4870 if (name === qname) {
4871 continue;
4872 }
4873 addWhereLineFn();
4874 const line = document.createElement("div");
4875 line.className = "where";
4876 line.appendChild(document.createTextNode(` ${qname} matches `));
4877 const lineStrong = document.createElement("strong");
4878 lineStrong.appendChild(document.createTextNode(name));
4879 line.appendChild(lineStrong);
4880 displayType.appendChild(line);
4714 addWhereLineFn();
4715 const line = document.createElement("div");
4716 line.className = "where";
4717 line.appendChild(document.createTextNode(` ${qname} matches `));
4718 const lineStrong = document.createElement("strong");
4719 lineStrong.appendChild(document.createTextNode(name));
4720 line.appendChild(lineStrong);
4721 displayType.appendChild(line);
4722 }
4723 for (const [name, innerType] of whereClause) {
4724 // don't care unless there's at least one highlighted entry
4725 if (innerType.length <= 1) {
4726 continue;
48814727 }
4882 for (const [name, innerType] of whereClause) {
4883 // don't care unless there's at least one highlighted entry
4884 if (innerType.length <= 1) {
4885 continue;
4728 addWhereLineFn();
4729 const line = document.createElement("div");
4730 line.className = "where";
4731 line.appendChild(document.createTextNode(` ${name}: `));
4732 innerType.forEach((value, index) => {
4733 if (index % 2 !== 0) {
4734 const highlight = document.createElement("strong");
4735 highlight.appendChild(document.createTextNode(value));
4736 line.appendChild(highlight);
4737 } else {
4738 line.appendChild(document.createTextNode(value));
48864739 }
4887 addWhereLineFn();
4888 const line = document.createElement("div");
4889 line.className = "where";
4890 line.appendChild(document.createTextNode(` ${name}: `));
4891 // @ts-expect-error
4892 innerType.forEach((value, index) => {
4893 if (index % 2 !== 0) {
4894 const highlight = document.createElement("strong");
4895 highlight.appendChild(document.createTextNode(value));
4896 line.appendChild(highlight);
4897 } else {
4898 line.appendChild(document.createTextNode(value));
4899 }
4900 });
4901 displayType.appendChild(line);
4902 }
4740 });
4741 displayType.appendChild(line);
49034742 }
4904 displayType.className = "type-signature";
4905 link.appendChild(displayType);
49064743 }
4744 displayType.className = "type-signature";
4745 link.appendChild(displayType);
4746 }
4747
4748 link.appendChild(description);
4749 output.appendChild(link);
49074750
4908 link.appendChild(description);
4909 return link;
4910 }));
4911 lis.then(lis => {
4912 for (const li of lis) {
4913 output.appendChild(li);
4751 results.next().then(async nextResult => {
4752 if (nextResult.value) {
4753 addNextResultToOutput(nextResult.value);
4754 } else {
4755 await Promise.all(descList);
4756 // need to make sure the element is shown before
4757 // running this callback
4758 yieldToBrowser().then(() => finishedCallback(count, output));
49144759 }
49154760 });
4916 } else if (query.error === null) {
4917 const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`;
4761 };
4762 const firstResult = await results.next();
4763 let correctionOutput = "";
4764 if (query.correction !== null && isTypeSearch) {
4765 const orig = query.returned.length > 0
4766 ? query.returned[0].name
4767 : query.elems[0].name;
4768 correctionOutput = "<h3 class=\"search-corrections\">" +
4769 `Type "${orig}" not found. ` +
4770 "Showing results for closest type name " +
4771 `"${query.correction}" instead.</h3>`;
4772 }
4773 if (query.proposeCorrectionFrom !== null && isTypeSearch) {
4774 const orig = query.proposeCorrectionFrom;
4775 const targ = query.proposeCorrectionTo;
4776 correctionOutput = "<h3 class=\"search-corrections\">" +
4777 `Type "${orig}" not found and used as generic parameter. ` +
4778 `Consider searching for "${targ}" instead.</h3>`;
4779 }
4780 if (firstResult.value) {
4781 if (correctionOutput !== "") {
4782 const h3 = document.createElement("h3");
4783 h3.innerHTML = correctionOutput;
4784 output.appendChild(h3);
4785 }
4786 await addNextResultToOutput(firstResult.value);
4787 } else {
4788 output = document.createElement("div");
4789 if (correctionOutput !== "") {
4790 const h3 = document.createElement("h3");
4791 h3.innerHTML = correctionOutput;
4792 output.appendChild(h3);
4793 }
49184794 output.className = "search-failed" + extraClass;
4919 output.innerHTML = "No results :(<br/>" +
4795 const dlroChannel = `https://doc.rust-lang.org/${getVar("channel")}`;
4796 if (query.userQuery !== "") {
4797 output.innerHTML += "No results :(<br/>" +
49204798 "Try on <a href=\"https://duckduckgo.com/?q=" +
49214799 encodeURIComponent("rust " + query.userQuery) +
49224800 "\">DuckDuckGo</a>?<br/><br/>" +
......@@ -4929,192 +4807,198 @@ ${item.displayPath}<span class="${type}">${name}</span>\
49294807 "introductions to language features and the language itself.</li><li><a " +
49304808 "href=\"https://docs.rs\">Docs.rs</a> for documentation of crates released on" +
49314809 " <a href=\"https://crates.io/\">crates.io</a>.</li></ul>";
4810 }
4811 output.innerHTML += "Example searches:<ul>" +
4812 "<li><a href=\"" + getNakedUrl() + "?search=std::vec\">std::vec</a></li>" +
4813 "<li><a href=\"" + getNakedUrl() + "?search=u32+->+bool\">u32 -> bool</a></li>" +
4814 "<li><a href=\"" + getNakedUrl() + "?search=Option<T>,+(T+->+U)+->+Option<U>\">" +
4815 "Option&lt;T>, (T -> U) -> Option&lt;U></a></li>" +
4816 "</ul>";
4817 // need to make sure the element is shown before
4818 // running this callback
4819 yieldToBrowser().then(() => finishedCallback(0, output));
49324820 }
49334821 return output;
49344822}
49354823
4936// @ts-expect-error
4937function makeTabHeader(tabNb, text, nbElems) {
4938 // https://blog.horizon-eda.org/misc/2020/02/19/ui.html
4939 //
4940 // CSS runs with `font-variant-numeric: tabular-nums` to ensure all
4941 // digits are the same width. \u{2007} is a Unicode space character
4942 // that is defined to be the same width as a digit.
4943 const fmtNbElems =
4944 nbElems < 10 ? `\u{2007}(${nbElems})\u{2007}\u{2007}` :
4945 nbElems < 100 ? `\u{2007}(${nbElems})\u{2007}` : `\u{2007}(${nbElems})`;
4946 if (searchState.currentTab === tabNb) {
4947 return "<button class=\"selected\">" + text +
4948 "<span class=\"count\">" + fmtNbElems + "</span></button>";
4949 }
4950 return "<button>" + text + "<span class=\"count\">" + fmtNbElems + "</span></button>";
4824/**
4825 * returns [tab, output]
4826 * @param {number} tabNb
4827 * @param {string} text
4828 * @param {AsyncGenerator<rustdoc.ResultObject>} results
4829 * @param {rustdoc.ParsedQuery<rustdoc.ParserQueryElement>} query
4830 * @param {boolean} isTypeSearch
4831 * @param {boolean} goToFirst
4832 * @returns {[HTMLElement, Promise<HTMLElement>]}
4833 */
4834function makeTab(tabNb, text, results, query, isTypeSearch, goToFirst) {
4835 const isCurrentTab = window.searchState.currentTab === tabNb;
4836 const tabButton = document.createElement("button");
4837 tabButton.appendChild(document.createTextNode(text));
4838 tabButton.className = isCurrentTab ? "selected" : "";
4839 const tabCount = document.createElement("span");
4840 tabCount.className = "count loading";
4841 tabCount.innerHTML = "\u{2007}(\u{2007})\u{2007}\u{2007}";
4842 tabButton.appendChild(tabCount);
4843 return [
4844 tabButton,
4845 addTab(results, query, isCurrentTab, (count, output) => {
4846 const search = window.searchState.outputElement();
4847 const error = query.error;
4848 if (count === 0 && error !== null && search) {
4849 error.forEach((value, index) => {
4850 value = value.split("<").join("&lt;").split(">").join("&gt;");
4851 if (index % 2 !== 0) {
4852 error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
4853 } else {
4854 error[index] = value;
4855 }
4856 });
4857 const errorReport = document.createElement("h3");
4858 errorReport.className = "error";
4859 errorReport.innerHTML = `Query parser error: "${error.join("")}".`;
4860 search.insertBefore(errorReport, search.firstElementChild);
4861 } else if (goToFirst ||
4862 (count === 1 && getSettingValue("go-to-only-result") === "true")
4863 ) {
4864 // Needed to force re-execution of JS when coming back to a page. Let's take this
4865 // scenario as example:
4866 //
4867 // 1. You have the "Directly go to item in search if there is only one result"
4868 // option enabled.
4869 // 2. You make a search which results only one result, leading you automatically to
4870 // this result.
4871 // 3. You go back to previous page.
4872 //
4873 // Now, without the call below, the JS will not be re-executed and the previous
4874 // state will be used, starting search again since the search input is not empty,
4875 // leading you back to the previous page again.
4876 window.onunload = () => { };
4877 window.searchState.removeQueryParameters();
4878 const a = output.querySelector("a");
4879 if (a) {
4880 a.click();
4881 return;
4882 }
4883 }
4884
4885 // https://blog.horizon-eda.org/misc/2020/02/19/ui.html
4886 //
4887 // CSS runs with `font-variant-numeric: tabular-nums` to ensure all
4888 // digits are the same width. \u{2007} is a Unicode space character
4889 // that is defined to be the same width as a digit.
4890 const fmtNbElems =
4891 count < 10 ? `\u{2007}(${count})\u{2007}\u{2007}` :
4892 count < 100 ? `\u{2007}(${count})\u{2007}` : `\u{2007}(${count})`;
4893 tabCount.innerHTML = fmtNbElems;
4894 tabCount.className = "count";
4895 }, isTypeSearch),
4896 ];
49514897}
49524898
49534899/**
4900 * @param {DocSearch} docSearch
49544901 * @param {rustdoc.ResultsTable} results
4955 * @param {boolean} go_to_first
4902 * @param {boolean} goToFirst
49564903 * @param {string} filterCrates
49574904 */
4958async function showResults(results, go_to_first, filterCrates) {
4959 const search = searchState.outputElement();
4960 if (go_to_first || (results.others.length === 1
4961 && getSettingValue("go-to-only-result") === "true")
4962 ) {
4963 // Needed to force re-execution of JS when coming back to a page. Let's take this
4964 // scenario as example:
4965 //
4966 // 1. You have the "Directly go to item in search if there is only one result" option
4967 // enabled.
4968 // 2. You make a search which results only one result, leading you automatically to
4969 // this result.
4970 // 3. You go back to previous page.
4971 //
4972 // Now, without the call below, the JS will not be re-executed and the previous state
4973 // will be used, starting search again since the search input is not empty, leading you
4974 // back to the previous page again.
4975 window.onunload = () => { };
4976 searchState.removeQueryParameters();
4977 const elem = document.createElement("a");
4978 elem.href = results.others[0].href;
4979 removeClass(elem, "active");
4980 // For firefox, we need the element to be in the DOM so it can be clicked.
4981 document.body.appendChild(elem);
4982 elem.click();
4983 return;
4984 }
4985 if (results.query === undefined) {
4986 // @ts-expect-error
4987 results.query = DocSearch.parseQuery(searchState.input.value);
4988 }
4905async function showResults(docSearch, results, goToFirst, filterCrates) {
4906 const search = window.searchState.outputElement();
49894907
4990 currentResults = results.query.userQuery;
4991
4992 // Navigate to the relevant tab if the current tab is empty, like in case users search
4993 // for "-> String". If they had selected another tab previously, they have to click on
4994 // it again.
4995 let currentTab = searchState.currentTab;
4996 if ((currentTab === 0 && results.others.length === 0) ||
4997 (currentTab === 1 && results.in_args.length === 0) ||
4998 (currentTab === 2 && results.returned.length === 0)) {
4999 if (results.others.length !== 0) {
5000 currentTab = 0;
5001 } else if (results.in_args.length) {
5002 currentTab = 1;
5003 } else if (results.returned.length) {
5004 currentTab = 2;
5005 }
4908 if (!search) {
4909 return;
50064910 }
50074911
50084912 let crates = "";
5009 if (rawSearchIndex.size > 1) {
5010 crates = "<div class=\"sub-heading\"> in&nbsp;<div id=\"crate-search-div\">" +
4913 const crateNames = await docSearch.getCrateNameList();
4914 if (crateNames.length > 1) {
4915 crates = "&nbsp;in&nbsp;<div id=\"crate-search-div\">" +
50114916 "<select id=\"crate-search\"><option value=\"all crates\">all crates</option>";
5012 for (const c of rawSearchIndex.keys()) {
4917 const l = crateNames.length;
4918 for (let i = 0; i < l; i += 1) {
4919 const c = crateNames[i];
50134920 crates += `<option value="${c}" ${c === filterCrates && "selected"}>${c}</option>`;
50144921 }
5015 crates += "</select></div></div>";
4922 crates += "</select></div>";
50164923 }
4924 nonnull(document.querySelector(".search-switcher")).innerHTML = `Search results${crates}`;
50174925
5018 let output = `<div class="main-heading">\
5019 <h1 class="search-results-title">Results</h1>${crates}</div>`;
4926 /** @type {[HTMLElement, Promise<HTMLElement>][]} */
4927 const tabs = [];
4928 searchState.currentTab = 0;
50204929 if (results.query.error !== null) {
5021 const error = results.query.error;
5022 // @ts-expect-error
5023 error.forEach((value, index) => {
5024 value = value.split("<").join("&lt;").split(">").join("&gt;");
5025 if (index % 2 !== 0) {
5026 error[index] = `<code>${value.replaceAll(" ", "&nbsp;")}</code>`;
5027 } else {
5028 error[index] = value;
5029 }
5030 });
5031 output += `<h3 class="error">Query parser error: "${error.join("")}".</h3>`;
5032 output += "<div id=\"search-tabs\">" +
5033 makeTabHeader(0, "In Names", results.others.length) +
5034 "</div>";
5035 currentTab = 0;
5036 } else if (results.query.foundElems <= 1 && results.query.returned.length === 0) {
5037 output += "<div id=\"search-tabs\">" +
5038 makeTabHeader(0, "In Names", results.others.length) +
5039 makeTabHeader(1, "In Parameters", results.in_args.length) +
5040 makeTabHeader(2, "In Return Types", results.returned.length) +
5041 "</div>";
4930 tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst));
4931 } else if (
4932 results.query.foundElems <= 1 &&
4933 results.query.returned.length === 0 &&
4934 !results.query.hasReturnArrow
4935 ) {
4936 tabs.push(makeTab(0, "In Names", results.others, results.query, false, goToFirst));
4937 tabs.push(makeTab(1, "In Parameters", results.in_args, results.query, true, false));
4938 tabs.push(makeTab(2, "In Return Types", results.returned, results.query, true, false));
50424939 } else {
50434940 const signatureTabTitle =
50444941 results.query.elems.length === 0 ? "In Function Return Types" :
50454942 results.query.returned.length === 0 ? "In Function Parameters" :
50464943 "In Function Signatures";
5047 output += "<div id=\"search-tabs\">" +
5048 makeTabHeader(0, signatureTabTitle, results.others.length) +
5049 "</div>";
5050 currentTab = 0;
5051 }
5052
5053 if (results.query.correction !== null) {
5054 const orig = results.query.returned.length > 0
5055 ? results.query.returned[0].name
5056 : results.query.elems[0].name;
5057 output += "<h3 class=\"search-corrections\">" +
5058 `Type "${orig}" not found. ` +
5059 "Showing results for closest type name " +
5060 `"${results.query.correction}" instead.</h3>`;
5061 }
5062 if (results.query.proposeCorrectionFrom !== null) {
5063 const orig = results.query.proposeCorrectionFrom;
5064 const targ = results.query.proposeCorrectionTo;
5065 output += "<h3 class=\"search-corrections\">" +
5066 `Type "${orig}" not found and used as generic parameter. ` +
5067 `Consider searching for "${targ}" instead.</h3>`;
4944 tabs.push(makeTab(0, signatureTabTitle, results.others, results.query, true, goToFirst));
50684945 }
50694946
5070 const [ret_others, ret_in_args, ret_returned] = await Promise.all([
5071 addTab(results.others, results.query, currentTab === 0),
5072 addTab(results.in_args, results.query, currentTab === 1),
5073 addTab(results.returned, results.query, currentTab === 2),
5074 ]);
4947 const tabsElem = document.createElement("div");
4948 tabsElem.id = "search-tabs";
50754949
50764950 const resultsElem = document.createElement("div");
50774951 resultsElem.id = "results";
5078 resultsElem.appendChild(ret_others);
5079 resultsElem.appendChild(ret_in_args);
5080 resultsElem.appendChild(ret_returned);
50814952
5082 // @ts-expect-error
5083 search.innerHTML = output;
5084 if (searchState.rustdocToolbar) {
5085 // @ts-expect-error
5086 search.querySelector(".main-heading").appendChild(searchState.rustdocToolbar);
4953 search.innerHTML = "";
4954 for (const [tab, output] of tabs) {
4955 tabsElem.appendChild(tab);
4956 const placeholder = document.createElement("div");
4957 output.then(output => {
4958 if (placeholder.parentElement) {
4959 placeholder.parentElement.replaceChild(output, placeholder);
4960 }
4961 });
4962 resultsElem.appendChild(placeholder);
4963 }
4964
4965 if (window.searchState.rustdocToolbar) {
4966 nonnull(
4967 nonnull(window.searchState.containerElement())
4968 .querySelector(".main-heading"),
4969 ).appendChild(window.searchState.rustdocToolbar);
50874970 }
50884971 const crateSearch = document.getElementById("crate-search");
50894972 if (crateSearch) {
50904973 crateSearch.addEventListener("input", updateCrate);
50914974 }
5092 // @ts-expect-error
4975 search.appendChild(tabsElem);
50934976 search.appendChild(resultsElem);
50944977 // Reset focused elements.
5095 searchState.showResults(search);
5096 // @ts-expect-error
5097 const elems = document.getElementById("search-tabs").childNodes;
5098 // @ts-expect-error
5099 searchState.focusedByTab = [];
4978 window.searchState.showResults();
4979 window.searchState.focusedByTab = [null, null, null];
51004980 let i = 0;
5101 for (const elem of elems) {
4981 for (const elem of tabsElem.childNodes) {
51024982 const j = i;
51034983 // @ts-expect-error
51044984 elem.onclick = () => printTab(j);
5105 searchState.focusedByTab.push(null);
4985 window.searchState.focusedByTab[i] = null;
51064986 i += 1;
51074987 }
5108 printTab(currentTab);
4988 printTab(0);
51094989}
51104990
51114991// @ts-expect-error
51124992function updateSearchHistory(url) {
4993 const btn = document.querySelector("#search-button a");
4994 if (btn instanceof HTMLAnchorElement) {
4995 btn.href = url;
4996 }
51134997 if (!browserSupportsHistoryApi()) {
51144998 return;
51154999 }
51165000 const params = searchState.getQueryStringParams();
5117 if (!history.state && !params.search) {
5001 if (!history.state && params.search === undefined) {
51185002 history.pushState(null, "", url);
51195003 } else {
51205004 history.replaceState(null, "", url);
......@@ -5127,8 +5011,8 @@ function updateSearchHistory(url) {
51275011 * @param {boolean} [forced]
51285012 */
51295013async function search(forced) {
5130 // @ts-expect-error
5131 const query = DocSearch.parseQuery(searchState.input.value.trim());
5014 const query = DocSearch.parseQuery(nonnull(window.searchState.inputElement()).value.trim());
5015
51325016 let filterCrates = getFilterCrates();
51335017
51345018 // @ts-expect-error
......@@ -5138,6 +5022,7 @@ async function search(forced) {
51385022 }
51395023 return;
51405024 }
5025 currentResults = query.userQuery;
51415026
51425027 searchState.setLoadingSearch();
51435028
......@@ -5149,6 +5034,12 @@ async function search(forced) {
51495034 filterCrates = params["filter-crate"];
51505035 }
51515036
5037 if (filterCrates !== null &&
5038 (await docSearch.getCrateNameList()).indexOf(filterCrates) === -1
5039 ) {
5040 filterCrates = null;
5041 }
5042
51525043 // Update document title to maintain a meaningful browser history
51535044 searchState.title = "\"" + query.userQuery + "\" Search - Rust";
51545045
......@@ -5157,6 +5048,7 @@ async function search(forced) {
51575048 updateSearchHistory(buildUrl(query.userQuery, filterCrates));
51585049
51595050 await showResults(
5051 docSearch,
51605052 // @ts-expect-error
51615053 await docSearch.execQuery(query, filterCrates, window.currentCrate),
51625054 params.go_to_first,
......@@ -5176,16 +5068,14 @@ function onSearchSubmit(e) {
51765068}
51775069
51785070function putBackSearch() {
5179 const search_input = searchState.input;
5180 if (!searchState.input) {
5071 const search_input = window.searchState.inputElement();
5072 if (!search_input) {
51815073 return;
51825074 }
5183 // @ts-expect-error
51845075 if (search_input.value !== "" && !searchState.isDisplayed()) {
51855076 searchState.showResults();
51865077 if (browserSupportsHistoryApi()) {
51875078 history.replaceState(null, "",
5188 // @ts-expect-error
51895079 buildUrl(search_input.value, getFilterCrates()));
51905080 }
51915081 document.title = searchState.title;
......@@ -5199,30 +5089,21 @@ function registerSearchEvents() {
51995089 // but only if the input bar is empty. This avoid the obnoxious issue
52005090 // where you start trying to do a search, and the index loads, and
52015091 // suddenly your search is gone!
5202 // @ts-expect-error
5203 if (searchState.input.value === "") {
5204 // @ts-expect-error
5205 searchState.input.value = params.search || "";
5092 const inputElement = nonnull(window.searchState.inputElement());
5093 if (inputElement.value === "") {
5094 inputElement.value = params.search || "";
52065095 }
52075096
52085097 const searchAfter500ms = () => {
52095098 searchState.clearInputTimeout();
5210 // @ts-expect-error
5211 if (searchState.input.value.length === 0) {
5212 searchState.hideResults();
5213 } else {
5214 // @ts-ignore
5215 searchState.timeout = setTimeout(search, 500);
5216 }
5099 window.searchState.timeout = setTimeout(search, 500);
52175100 };
5218 // @ts-expect-error
5219 searchState.input.onkeyup = searchAfter500ms;
5220 // @ts-expect-error
5221 searchState.input.oninput = searchAfter500ms;
5222 // @ts-expect-error
5223 document.getElementsByClassName("search-form")[0].onsubmit = onSearchSubmit;
5224 // @ts-expect-error
5225 searchState.input.onchange = e => {
5101 inputElement.onkeyup = searchAfter500ms;
5102 inputElement.oninput = searchAfter500ms;
5103 if (inputElement.form) {
5104 inputElement.form.onsubmit = onSearchSubmit;
5105 }
5106 inputElement.onchange = e => {
52265107 if (e.target !== document.activeElement) {
52275108 // To prevent doing anything when it's from a blur event.
52285109 return;
......@@ -5234,11 +5115,13 @@ function registerSearchEvents() {
52345115 // change, though.
52355116 setTimeout(search, 0);
52365117 };
5237 // @ts-expect-error
5238 searchState.input.onpaste = searchState.input.onchange;
5118 inputElement.onpaste = inputElement.onchange;
52395119
52405120 // @ts-expect-error
52415121 searchState.outputElement().addEventListener("keydown", e => {
5122 if (!(e instanceof KeyboardEvent)) {
5123 return;
5124 }
52425125 // We only handle unmodified keystrokes here. We don't want to interfere with,
52435126 // for instance, alt-left and alt-right for history navigation.
52445127 if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {
......@@ -5278,88 +5161,23 @@ function registerSearchEvents() {
52785161 }
52795162 });
52805163
5281 // @ts-expect-error
5282 searchState.input.addEventListener("keydown", e => {
5164 inputElement.addEventListener("keydown", e => {
52835165 if (e.which === 40) { // down
52845166 focusSearchResult();
52855167 e.preventDefault();
52865168 }
52875169 });
52885170
5289 // @ts-expect-error
5290 searchState.input.addEventListener("focus", () => {
5171 inputElement.addEventListener("focus", () => {
52915172 putBackSearch();
52925173 });
5293
5294 // @ts-expect-error
5295 searchState.input.addEventListener("blur", () => {
5296 if (window.searchState.input) {
5297 window.searchState.input.placeholder = window.searchState.origPlaceholder;
5298 }
5299 });
5300
5301 // Push and pop states are used to add search results to the browser
5302 // history.
5303 if (browserSupportsHistoryApi()) {
5304 // Store the previous <title> so we can revert back to it later.
5305 const previousTitle = document.title;
5306
5307 window.addEventListener("popstate", e => {
5308 const params = searchState.getQueryStringParams();
5309 // Revert to the previous title manually since the History
5310 // API ignores the title parameter.
5311 document.title = previousTitle;
5312 // When browsing forward to search results the previous
5313 // search will be repeated, so the currentResults are
5314 // cleared to ensure the search is successful.
5315 currentResults = null;
5316 // Synchronize search bar with query string state and
5317 // perform the search. This will empty the bar if there's
5318 // nothing there, which lets you really go back to a
5319 // previous state with nothing in the bar.
5320 if (params.search && params.search.length > 0) {
5321 // @ts-expect-error
5322 searchState.input.value = params.search;
5323 // Some browsers fire "onpopstate" for every page load
5324 // (Chrome), while others fire the event only when actually
5325 // popping a state (Firefox), which is why search() is
5326 // called both here and at the end of the startSearch()
5327 // function.
5328 e.preventDefault();
5329 search();
5330 } else {
5331 // @ts-expect-error
5332 searchState.input.value = "";
5333 // When browsing back from search results the main page
5334 // visibility must be reset.
5335 searchState.hideResults();
5336 }
5337 });
5338 }
5339
5340 // This is required in firefox to avoid this problem: Navigating to a search result
5341 // with the keyboard, hitting enter, and then hitting back would take you back to
5342 // the doc page, rather than the search that should overlay it.
5343 // This was an interaction between the back-forward cache and our handlers
5344 // that try to sync state between the URL and the search input. To work around it,
5345 // do a small amount of re-init on page show.
5346 window.onpageshow = () => {
5347 const qSearch = searchState.getQueryStringParams().search;
5348 // @ts-expect-error
5349 if (searchState.input.value === "" && qSearch) {
5350 // @ts-expect-error
5351 searchState.input.value = qSearch;
5352 }
5353 search();
5354 };
53555174}
53565175
53575176// @ts-expect-error
53585177function updateCrate(ev) {
53595178 if (ev.target.value === "all crates") {
53605179 // If we don't remove it from the URL, it'll be picked up again by the search.
5361 // @ts-expect-error
5362 const query = searchState.input.value.trim();
5180 const query = nonnull(window.searchState.inputElement()).value.trim();
53635181 updateSearchHistory(buildUrl(query, null));
53645182 }
53655183 // In case you "cut" the entry from the search input, then change the crate filter
......@@ -5369,522 +5187,91 @@ function updateCrate(ev) {
53695187 search(true);
53705188}
53715189
5372// Parts of this code are based on Lucene, which is licensed under the
5373// Apache/2.0 license.
5374// More information found here:
5375// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/
5376// LevenshteinAutomata.java
5377class ParametricDescription {
5378 // @ts-expect-error
5379 constructor(w, n, minErrors) {
5380 this.w = w;
5381 this.n = n;
5382 this.minErrors = minErrors;
5383 }
5384 // @ts-expect-error
5385 isAccept(absState) {
5386 const state = Math.floor(absState / (this.w + 1));
5387 const offset = absState % (this.w + 1);
5388 return this.w - offset + this.minErrors[state] <= this.n;
5389 }
5390 // @ts-expect-error
5391 getPosition(absState) {
5392 return absState % (this.w + 1);
5393 }
5394 // @ts-expect-error
5395 getVector(name, charCode, pos, end) {
5396 let vector = 0;
5397 for (let i = pos; i < end; i += 1) {
5398 vector = vector << 1;
5399 if (name.charCodeAt(i) === charCode) {
5400 vector |= 1;
5401 }
5402 }
5403 return vector;
5404 }
5405 // @ts-expect-error
5406 unpack(data, index, bitsPerValue) {
5407 const bitLoc = (bitsPerValue * index);
5408 const dataLoc = bitLoc >> 5;
5409 const bitStart = bitLoc & 31;
5410 if (bitStart + bitsPerValue <= 32) {
5411 // not split
5412 return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]);
5413 } else {
5414 // split
5415 const part = 32 - bitStart;
5416 return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) +
5417 ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part));
5418 }
5190// eslint-disable-next-line max-len
5191// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64
5192/**
5193 * @type {function(string): Uint8Array} base64
5194 */
5195//@ts-expect-error
5196const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => {
5197 const bytes_as_string = atob(string);
5198 const l = bytes_as_string.length;
5199 const bytes = new Uint8Array(l);
5200 for (let i = 0; i < l; ++i) {
5201 bytes[i] = bytes_as_string.charCodeAt(i);
54195202 }
5420}
5421ParametricDescription.prototype.MASKS = new Int32Array([
5422 0x1, 0x3, 0x7, 0xF,
5423 0x1F, 0x3F, 0x7F, 0xFF,
5424 0x1FF, 0x3F, 0x7FF, 0xFFF,
5425 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
5426 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
5427 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
5428 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
5429 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
5430]);
5431
5432// The following code was generated with the moman/finenight pkg
5433// This package is available under the MIT License, see NOTICE.txt
5434// for more details.
5435// This class is auto-generated, Please do not modify it directly.
5436// You should modify the https://gitlab.com/notriddle/createAutomata.py instead.
5437// The following code was generated with the moman/finenight pkg
5438// This package is available under the MIT License, see NOTICE.txt
5439// for more details.
5440// This class is auto-generated, Please do not modify it directly.
5441// You should modify https://gitlab.com/notriddle/moman-rustdoc instead.
5442
5443class Lev2TParametricDescription extends ParametricDescription {
5444 /**
5445 * @param {number} absState
5446 * @param {number} position
5447 * @param {number} vector
5448 * @returns {number}
5449 */
5450 transition(absState, position, vector) {
5451 let state = Math.floor(absState / (this.w + 1));
5452 let offset = absState % (this.w + 1);
5453
5454 if (position === this.w) {
5455 if (state < 3) { // eslint-disable-line no-lonely-if
5456 const loc = Math.imul(vector, 3) + state;
5457 offset += this.unpack(this.offsetIncrs0, loc, 1);
5458 state = this.unpack(this.toStates0, loc, 2) - 1;
5459 }
5460 } else if (position === this.w - 1) {
5461 if (state < 5) { // eslint-disable-line no-lonely-if
5462 const loc = Math.imul(vector, 5) + state;
5463 offset += this.unpack(this.offsetIncrs1, loc, 1);
5464 state = this.unpack(this.toStates1, loc, 3) - 1;
5465 }
5466 } else if (position === this.w - 2) {
5467 if (state < 13) { // eslint-disable-line no-lonely-if
5468 const loc = Math.imul(vector, 13) + state;
5469 offset += this.unpack(this.offsetIncrs2, loc, 2);
5470 state = this.unpack(this.toStates2, loc, 4) - 1;
5471 }
5472 } else if (position === this.w - 3) {
5473 if (state < 28) { // eslint-disable-line no-lonely-if
5474 const loc = Math.imul(vector, 28) + state;
5475 offset += this.unpack(this.offsetIncrs3, loc, 2);
5476 state = this.unpack(this.toStates3, loc, 5) - 1;
5477 }
5478 } else if (position === this.w - 4) {
5479 if (state < 45) { // eslint-disable-line no-lonely-if
5480 const loc = Math.imul(vector, 45) + state;
5481 offset += this.unpack(this.offsetIncrs4, loc, 3);
5482 state = this.unpack(this.toStates4, loc, 6) - 1;
5483 }
5484 } else {
5485 if (state < 45) { // eslint-disable-line no-lonely-if
5486 const loc = Math.imul(vector, 45) + state;
5487 offset += this.unpack(this.offsetIncrs5, loc, 3);
5488 state = this.unpack(this.toStates5, loc, 6) - 1;
5489 }
5490 }
5203 return bytes;
5204});
54915205
5492 if (state === -1) {
5493 // null state
5494 return -1;
5495 } else {
5496 // translate back to abs
5497 return Math.imul(state, this.w + 1) + offset;
5498 }
5499 }
55005206
5501 // state map
5502 // 0 -> [(0, 0)]
5503 // 1 -> [(0, 1)]
5504 // 2 -> [(0, 2)]
5505 // 3 -> [(0, 1), (1, 1)]
5506 // 4 -> [(0, 2), (1, 2)]
5507 // 5 -> [(0, 1), (1, 1), (2, 1)]
5508 // 6 -> [(0, 2), (1, 2), (2, 2)]
5509 // 7 -> [(0, 1), (2, 1)]
5510 // 8 -> [(0, 1), (2, 2)]
5511 // 9 -> [(0, 2), (2, 1)]
5512 // 10 -> [(0, 2), (2, 2)]
5513 // 11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
5514 // 12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)]
5515 // 13 -> [(0, 2), (1, 2), (2, 2), (3, 2)]
5516 // 14 -> [(0, 1), (1, 1), (3, 2)]
5517 // 15 -> [(0, 1), (2, 2), (3, 2)]
5518 // 16 -> [(0, 1), (3, 2)]
5519 // 17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)]
5520 // 18 -> [(0, 2), (1, 2), (3, 1)]
5521 // 19 -> [(0, 2), (1, 2), (3, 2)]
5522 // 20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)]
5523 // 21 -> [(0, 2), (2, 1), (3, 1)]
5524 // 22 -> [(0, 2), (2, 2), (3, 2)]
5525 // 23 -> [(0, 2), (3, 1)]
5526 // 24 -> [(0, 2), (3, 2)]
5527 // 25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)]
5528 // 26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)]
5529 // 27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)]
5530 // 28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
5531 // 29 -> [(0, 2), (1, 2), (2, 2), (4, 2)]
5532 // 30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
5533 // 31 -> [(0, 2), (1, 2), (3, 2), (4, 2)]
5534 // 32 -> [(0, 2), (1, 2), (4, 2)]
5535 // 33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)]
5536 // 34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
5537 // 35 -> [(0, 2), (2, 1), (4, 2)]
5538 // 36 -> [(0, 2), (2, 2), (3, 2), (4, 2)]
5539 // 37 -> [(0, 2), (2, 2), (4, 2)]
5540 // 38 -> [(0, 2), (3, 2), (4, 2)]
5541 // 39 -> [(0, 2), (4, 2)]
5542 // 40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
5543 // 41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
5544 // 42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
5545 // 43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)]
5546 // 44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
5547
5548
5549 /** @param {number} w - length of word being checked */
5550 constructor(w) {
5551 super(w, 2, new Int32Array([
5552 0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2,
5553 -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,
5554 ]));
5207if (ROOT_PATH === null) {
5208 return;
5209}
5210const database = await Stringdex.loadDatabase(hooks);
5211if (typeof window !== "undefined") {
5212 docSearch = new DocSearch(ROOT_PATH, database);
5213 await docSearch.buildIndex();
5214 onEachLazy(document.querySelectorAll(
5215 ".search-form.loading",
5216 ), form => {
5217 removeClass(form, "loading");
5218 });
5219 registerSearchEvents();
5220 // If there's a search term in the URL, execute the search now.
5221 if (window.searchState.getQueryStringParams().search !== undefined) {
5222 search();
55555223 }
5224} else if (typeof exports !== "undefined") {
5225 docSearch = new DocSearch(ROOT_PATH, database);
5226 await docSearch.buildIndex();
5227 return { docSearch, DocSearch };
55565228}
5229};
55575230
5558Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
5559 0xe,
5560]);
5561Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
5562 0x0,
5563]);
5564
5565Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([
5566 0x1a688a2c,
5567]);
5568Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
5569 0x3e0,
5570]);
5571
5572Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([
5573 0x70707054,0xdc07035,0x3dd3a3a,0x2323213a,
5574 0x15435223,0x22545432,0x5435,
5575]);
5576Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
5577 0x80000,0x55582088,0x55555555,0x55,
5578]);
5579
5580Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([
5581 0x1c0380a4,0x700a570,0xca529c0,0x180a00,
5582 0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8,
5583 0xac18c601,0xd8d43501,0x863500ad,0x51976d6a,
5584 0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5,
5585 0x18c4519,0xc41294a,0xe248d231,0x1086520c,
5586 0xce31ac42,0x13946358,0x2d0348c4,0x6732d494,
5587 0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948,
5588 0x22110a52,0x58ce729d,0xc41394e3,0x941cc520,
5589 0x90e732d4,0x4729d224,0x39ce35ad,
5590]);
5591Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
5592 0x80000,0xc0c830,0x300f3c30,0x2200fcff,
5593 0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555,
5594 0x55555555,0x55555555,0x55555555,0x55555555,
5595 0x55555555,0x55555555,
5596]);
5597
5598Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([
5599 0x801c0144,0x1453803,0x14700038,0xc0005145,
5600 0x1401,0x14,0x140000,0x0,
5601 0x510000,0x6301f007,0x301f00d1,0xa186178,
5602 0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd,
5603 0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34,
5604 0x8300550,0x430c0143,0x50c31,0xc30850c,
5605 0xc3143000,0x50053c50,0x5130d301,0x850d30c2,
5606 0x30a08608,0xc214414,0x43142145,0x21450031,
5607 0x1400c314,0x4c143145,0x32832803,0x28014d6c,
5608 0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3,
5609 0x1431,0xc300500,0xca00d303,0xd36d0e40,
5610 0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c,
5611 0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280,
5612 0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401,
5613 0x1430c714,0xc3087,0x71451450,0xca00d30,
5614 0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144,
5615 0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51,
5616 0x24324308,0xc51031c2,0x70820820,0x5c33830d,
5617 0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3,
5618 0x20c20c20,0xda0920d,0x5145914f,0x36596114,
5619 0x51965865,0xd9643653,0x365a6590,0x51964364,
5620 0x43081505,0x920b2032,0x2c718b28,0xd7242249,
5621 0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2,
5622 0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c,
5623 0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7,
5624 0x73975c36,0x10308138,0xc2245105,0x41451031,
5625 0x14e24208,0xc35c3387,0x51453851,0x1c51c514,
5626 0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092,
5627 0x4513d41,0x6533944d,0x1350e658,0xe1545055,
5628 0x64365a50,0x5519383,0x51030815,0x28920718,
5629 0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387,
5630 0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440,
5631 0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65,
5632 0x8e154387,0x364b5ca3,0x38739738,
5633]);
5634Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([
5635 0x10000000,0xc00000,0x60061,0x400,
5636 0x0,0x80010008,0x249248a4,0x8229048,
5637 0x2092,0x6c3603,0xb61b6c30,0x6db6036d,
5638 0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b,
5639 0x6db6236,0x1008200,0x12480012,0x24924906,
5640 0x48200049,0x80410002,0x24000900,0x4924a489,
5641 0x10822492,0x20800125,0x48360,0x9241b692,
5642 0x6da4924,0x40009268,0x241b010,0x291b4900,
5643 0x6d249249,0x49493423,0x92492492,0x24924924,
5644 0x49249249,0x92492492,0x24924924,0x49249249,
5645 0x92492492,0x24924924,0x49249249,0x92492492,
5646 0x24924924,0x49249249,0x92492492,0x24924924,
5647 0x49249249,0x92492492,0x24924924,0x49249249,
5648 0x92492492,0x24924924,0x49249249,0x92492492,
5649 0x24924924,0x49249249,0x92492492,0x24924924,
5650 0x49249249,0x92492492,0x24924924,0x49249249,
5651 0x92492492,0x24924924,0x49249249,0x2492,
5652]);
5653
5654Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([
5655 0x801c0144,0x1453803,0x14700038,0xc0005145,
5656 0x1401,0x14,0x140000,0x0,
5657 0x510000,0x4e00e007,0xe0051,0x3451451c,
5658 0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4,
5659 0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07,
5660 0x2830c286,0x830c3083,0xc30030,0x33430c,
5661 0x30c3003,0x70051030,0x16301f00,0x8301f00d,
5662 0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51,
5663 0x14314315,0x4f143145,0x34c05401,0x4c30944c,
5664 0x55150c3,0x30830055,0x1430c014,0xc00050c3,
5665 0xc30850,0xc314300,0x150053c5,0x25130d30,
5666 0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c,
5667 0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540,
5668 0x34c30944,0x82182214,0x851050c2,0x50851430,
5669 0x1400c50c,0x30c5085,0x50c51450,0x150053c,
5670 0xc25130d3,0x8850d30,0x1430a086,0x450c2144,
5671 0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1,
5672 0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c,
5673 0xc31c3140,0x31430c30,0x14,0x30c3005,
5674 0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3,
5675 0xc500d01,0x5965965a,0x30d46546,0x6435030c,
5676 0x8034c659,0xdb439032,0x2c390034,0xcaaecb24,
5677 0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033,
5678 0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e,
5679 0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce,
5680 0x95c53831,0x28034c5d,0x9b705583,0xa1455830,
5681 0xc76cd6c,0x40143085,0x71451c51,0x871430c,
5682 0x450000c3,0xd3071451,0x1560ca00,0x560c26dc,
5683 0xb35b2851,0xc914369,0x1a14500d,0x46593945,
5684 0xcb2c939,0x94507503,0x328034c3,0x9b70558,
5685 0xe41c5583,0x72caaeca,0x1c308510,0xc7147287,
5686 0x50871c32,0x1470030c,0xd307147,0xc1560ca0,
5687 0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c,
5688 0x8e657394,0x314b1c93,0x39438738,0x43083081,
5689 0x31c22432,0x820c510,0x830d7082,0x50c35c33,
5690 0xc30c338,0xc31c30c3,0x50c30c30,0xc204514,
5691 0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3,
5692 0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a,
5693 0x48308308,0xc3682483,0x14516453,0x4d965845,
5694 0xd4659619,0x36590d94,0xd969964,0x546590d9,
5695 0x20c20541,0x920d20c,0x5914f0da,0x96114514,
5696 0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e,
5697 0x81821827,0xb2032430,0x18b28920,0x422492c7,
5698 0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972,
5699 0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c,
5700 0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28,
5701 0x59a8a269,0x8175e7a,0xb203243,0x718b2892,
5702 0x4114105c,0x17597658,0x74ce5d96,0x5c36572d,
5703 0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8,
5704 0x4171c62,0x5d961045,0x976585d6,0x79669533,
5705 0x964965a2,0x659689e6,0x308175e7,0x24510510,
5706 0x451031c2,0xe2420841,0x5c338714,0x453851c3,
5707 0x51c51451,0xc30c31c,0x451450c7,0x41440c20,
5708 0xc708914,0x82105144,0xf1c58c90,0x1470ea0d,
5709 0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861,
5710 0x24853c51,0x5053c368,0x1341144f,0x96194ce5,
5711 0x1544d439,0x94385514,0xe0d90d96,0x5415464,
5712 0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0,
5713 0x350e6586,0x86082181,0xe89e981d,0x18277689,
5714 0x10308182,0x89207185,0x41c718b2,0x14e24224,
5715 0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb,
5716 0x204e1c75,0x1c61440c,0xc62ca248,0x90891071,
5717 0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475,
5718 0x5e7a57a8,0x51030817,0x28920718,0xf38718b,
5719 0xe5134114,0x39961758,0xe1ce4ce,0x728e3855,
5720 0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24,
5721 0xd04503ce,0x85d63944,0x75338e65,0x5d86075e,
5722 0x89e69647,0x75e76576,
5723]);
5724Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([
5725 0x10000000,0xc00000,0x60061,0x400,
5726 0x0,0x60000008,0x6b003080,0xdb6ab6db,
5727 0x2db6,0x800400,0x49245240,0x11482412,
5728 0x104904,0x40020000,0x92292000,0xa4b25924,
5729 0x9649658,0xd80c000,0xdb0c001b,0x80db6d86,
5730 0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d,
5731 0xddadb6ed,0x300001b6,0x6c360,0xe37236e4,
5732 0x46db6236,0xdb6c,0x361b018,0xb91b7200,
5733 0x6dbb1b71,0x6db763,0x20100820,0x61248001,
5734 0x92492490,0x24820004,0x8041000,0x92400090,
5735 0x24924830,0x555b6a49,0x2080012,0x20004804,
5736 0x49252449,0x84112492,0x4000928,0x240201,
5737 0x92922490,0x58924924,0x49456,0x120d8082,
5738 0x6da4800,0x69249249,0x249a01b,0x6c04100,
5739 0x6d240009,0x92492483,0x24d5adb4,0x60208001,
5740 0x92000483,0x24925236,0x6846da49,0x10400092,
5741 0x241b0,0x49291b49,0x636d2492,0x92494935,
5742 0x24924924,0x49249249,0x92492492,0x24924924,
5743 0x49249249,0x92492492,0x24924924,0x49249249,
5744 0x92492492,0x24924924,0x49249249,0x92492492,
5745 0x24924924,0x49249249,0x92492492,0x24924924,
5746 0x49249249,0x92492492,0x24924924,0x49249249,
5747 0x92492492,0x24924924,0x49249249,0x92492492,
5748 0x24924924,0x49249249,0x92492492,0x24924924,
5749 0x49249249,0x92492492,0x24924924,0x49249249,
5750 0x92492492,0x24924924,0x49249249,0x92492492,
5751 0x24924924,0x49249249,0x92492492,0x24924924,
5752 0x49249249,0x92492492,0x24924924,0x49249249,
5753 0x92492492,0x24924924,0x49249249,0x92492492,
5754 0x24924924,0x49249249,0x92492492,0x24924924,
5755 0x49249249,0x92492492,0x24924924,0x49249249,
5756 0x92492492,0x24924924,0x49249249,0x92492492,
5757 0x24924924,0x49249249,0x92492492,0x24924924,
5758 0x49249249,0x92492492,0x24924924,
5759]);
5760
5761class Lev1TParametricDescription extends ParametricDescription {
5762 /**
5763 * @param {number} absState
5764 * @param {number} position
5765 * @param {number} vector
5766 * @returns {number}
5767 */
5768 transition(absState, position, vector) {
5769 let state = Math.floor(absState / (this.w + 1));
5770 let offset = absState % (this.w + 1);
5771
5772 if (position === this.w) {
5773 if (state < 2) { // eslint-disable-line no-lonely-if
5774 const loc = Math.imul(vector, 2) + state;
5775 offset += this.unpack(this.offsetIncrs0, loc, 1);
5776 state = this.unpack(this.toStates0, loc, 2) - 1;
5777 }
5778 } else if (position === this.w - 1) {
5779 if (state < 3) { // eslint-disable-line no-lonely-if
5780 const loc = Math.imul(vector, 3) + state;
5781 offset += this.unpack(this.offsetIncrs1, loc, 1);
5782 state = this.unpack(this.toStates1, loc, 2) - 1;
5783 }
5784 } else if (position === this.w - 2) {
5785 if (state < 6) { // eslint-disable-line no-lonely-if
5786 const loc = Math.imul(vector, 6) + state;
5787 offset += this.unpack(this.offsetIncrs2, loc, 2);
5788 state = this.unpack(this.toStates2, loc, 3) - 1;
5231if (typeof window !== "undefined") {
5232 const ROOT_PATH = window.rootPath;
5233 /** @type {stringdex.Callbacks|null} */
5234 let databaseCallbacks = null;
5235 initSearch(window.Stringdex, window.RoaringBitmap, {
5236 loadRoot: callbacks => {
5237 for (const key in callbacks) {
5238 if (Object.hasOwn(callbacks, key)) {
5239 // @ts-ignore
5240 window[key] = callbacks[key];
5241 }
57895242 }
5790 } else {
5791 if (state < 6) { // eslint-disable-line no-lonely-if
5792 const loc = Math.imul(vector, 6) + state;
5793 offset += this.unpack(this.offsetIncrs3, loc, 2);
5794 state = this.unpack(this.toStates3, loc, 3) - 1;
5243 databaseCallbacks = callbacks;
5244 // search.index/root is loaded by main.js, so
5245 // this script doesn't need to launch it, but
5246 // must pick it up
5247 // @ts-ignore
5248 if (window.searchIndex) {
5249 // @ts-ignore
5250 window.rr_(window.searchIndex);
57955251 }
5796 }
5797
5798 if (state === -1) {
5799 // null state
5800 return -1;
5801 } else {
5802 // translate back to abs
5803 return Math.imul(state, this.w + 1) + offset;
5804 }
5805 }
5806
5807 // state map
5808 // 0 -> [(0, 0)]
5809 // 1 -> [(0, 1)]
5810 // 2 -> [(0, 1), (1, 1)]
5811 // 3 -> [(0, 1), (1, 1), (2, 1)]
5812 // 4 -> [(0, 1), (2, 1)]
5813 // 5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
5814
5815
5816 /** @param {number} w - length of word being checked */
5817 constructor(w) {
5818 super(w, 1, new Int32Array([0,1,0,-1,-1,-1]));
5819 }
5820}
5821
5822Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
5823 0x2,
5824]);
5825Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
5826 0x0,
5827]);
5828
5829Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([
5830 0xa43,
5831]);
5832Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
5833 0x38,
5834]);
5835
5836Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([
5837 0x12180003,0xb45a4914,0x69,
5838]);
5839Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
5840 0x558a0000,0x5555,
5841]);
5842
5843Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([
5844 0x900c0003,0xa1904864,0x45a49169,0x5a6d196a,
5845 0x9634,
5846]);
5847Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
5848 0xa0fc0000,0x5555ba08,0x55555555,
5849]);
5850
5851// ====================
5852// WARNING: Nothing should be added below this comment: we need the `initSearch` function to
5853// be called ONLY when the whole file has been parsed and loaded.
5854
5855// @ts-expect-error
5856function initSearch(searchIndex) {
5857 rawSearchIndex = searchIndex;
5858 if (typeof window !== "undefined") {
5859 // @ts-expect-error
5860 docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
5861 registerSearchEvents();
5862 // If there's a search term in the URL, execute the search now.
5863 if (window.searchState.getQueryStringParams().search) {
5864 search();
5865 }
5866 } else if (typeof exports !== "undefined") {
5867 // @ts-expect-error
5868 docSearch = new DocSearch(rawSearchIndex, ROOT_PATH, searchState);
5869 exports.docSearch = docSearch;
5870 exports.parseQuery = DocSearch.parseQuery;
5871 }
5872}
5873
5874if (typeof exports !== "undefined") {
5252 },
5253 loadTreeByHash: hashHex => {
5254 const script = document.createElement("script");
5255 script.src = `${ROOT_PATH}/search.index/${hashHex}.js`;
5256 script.onerror = e => {
5257 if (databaseCallbacks) {
5258 databaseCallbacks.err_rn_(hashHex, e);
5259 }
5260 };
5261 document.documentElement.appendChild(script);
5262 },
5263 loadDataByNameAndHash: (name, hashHex) => {
5264 const script = document.createElement("script");
5265 script.src = `${ROOT_PATH}/search.index/${name}/${hashHex}.js`;
5266 script.onerror = e => {
5267 if (databaseCallbacks) {
5268 databaseCallbacks.err_rd_(hashHex, e);
5269 }
5270 };
5271 document.documentElement.appendChild(script);
5272 },
5273 });
5274} else if (typeof exports !== "undefined") {
5275 // eslint-disable-next-line no-undef
58755276 exports.initSearch = initSearch;
58765277}
5877
5878if (typeof window !== "undefined") {
5879 // @ts-expect-error
5880 window.initSearch = initSearch;
5881 // @ts-expect-error
5882 if (window.searchIndex !== undefined) {
5883 // @ts-expect-error
5884 initSearch(window.searchIndex);
5885 }
5886} else {
5887 // Running in Node, not a browser. Run initSearch just to produce the
5888 // exports.
5889 initSearch(new Map());
5890}
src/librustdoc/html/static/js/settings.js+42-44
......@@ -1,25 +1,13 @@
11// Local js definitions:
22/* global getSettingValue, updateLocalStorage, updateTheme */
33/* global addClass, removeClass, onEach, onEachLazy */
4/* global MAIN_ID, getVar, getSettingsButton, getHelpButton, nonnull */
4/* global MAIN_ID, getVar, nonnull */
55
66"use strict";
77
88(function() {
99 const isSettingsPage = window.location.pathname.endsWith("/settings.html");
1010
11 /**
12 * @param {Element} elem
13 * @param {EventTarget|null} target
14 */
15 function elemContainsTarget(elem, target) {
16 if (target instanceof Node) {
17 return elem.contains(target);
18 } else {
19 return false;
20 }
21 }
22
2311 /**
2412 * @overload {"theme"|"preferred-dark-theme"|"preferred-light-theme"}
2513 * @param {string} settingName
......@@ -305,10 +293,12 @@
305293 }
306294 } else {
307295 el.setAttribute("tabindex", "-1");
308 const settingsBtn = getSettingsButton();
309 if (settingsBtn !== null) {
310 settingsBtn.appendChild(el);
311 }
296 onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
297 if (menu.offsetWidth !== 0) {
298 menu.appendChild(el);
299 return true;
300 }
301 });
312302 }
313303 return el;
314304 }
......@@ -317,6 +307,15 @@
317307
318308 function displaySettings() {
319309 settingsMenu.style.display = "";
310 onEachLazy(document.querySelectorAll(".settings-menu"), menu => {
311 if (menu.offsetWidth !== 0) {
312 if (!menu.contains(settingsMenu) && settingsMenu.parentElement) {
313 settingsMenu.parentElement.removeChild(settingsMenu);
314 menu.appendChild(settingsMenu);
315 }
316 return true;
317 }
318 });
320319 onEachLazy(settingsMenu.querySelectorAll("input[type='checkbox']"), el => {
321320 const val = getSettingValue(el.id);
322321 const checked = val === "true";
......@@ -330,40 +329,37 @@
330329 * @param {FocusEvent} event
331330 */
332331 function settingsBlurHandler(event) {
333 const helpBtn = getHelpButton();
334 const settingsBtn = getSettingsButton();
335 const helpUnfocused = helpBtn === null ||
336 (!helpBtn.contains(document.activeElement) &&
337 !elemContainsTarget(helpBtn, event.relatedTarget));
338 const settingsUnfocused = settingsBtn === null ||
339 (!settingsBtn.contains(document.activeElement) &&
340 !elemContainsTarget(settingsBtn, event.relatedTarget));
341 if (helpUnfocused && settingsUnfocused) {
332 const isInPopover = onEachLazy(
333 document.querySelectorAll(".settings-menu, .help-menu"),
334 menu => {
335 return menu.contains(document.activeElement) || menu.contains(event.relatedTarget);
336 },
337 );
338 if (!isInPopover) {
342339 window.hidePopoverMenus();
343340 }
344341 }
345342
346343 if (!isSettingsPage) {
347344 // We replace the existing "onclick" callback.
348 // These elements must exist, as (outside of the settings page)
349 // `settings.js` is only loaded after the settings button is clicked.
350 const settingsButton = nonnull(getSettingsButton());
351345 const settingsMenu = nonnull(document.getElementById("settings"));
352 settingsButton.onclick = event => {
353 if (elemContainsTarget(settingsMenu, event.target)) {
354 return;
355 }
356 event.preventDefault();
357 const shouldDisplaySettings = settingsMenu.style.display === "none";
346 onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
347 /** @param {MouseEvent} event */
348 settingsButton.querySelector("a").onclick = event => {
349 if (!(event.target instanceof Element) || settingsMenu.contains(event.target)) {
350 return;
351 }
352 event.preventDefault();
353 const shouldDisplaySettings = settingsMenu.style.display === "none";
358354
359 window.hideAllModals(false);
360 if (shouldDisplaySettings) {
361 displaySettings();
362 }
363 };
364 settingsButton.onblur = settingsBlurHandler;
365 // the settings button should always have a link in it
366 nonnull(settingsButton.querySelector("a")).onblur = settingsBlurHandler;
355 window.hideAllModals(false);
356 if (shouldDisplaySettings) {
357 displaySettings();
358 }
359 };
360 settingsButton.onblur = settingsBlurHandler;
361 settingsButton.querySelector("a").onblur = settingsBlurHandler;
362 });
367363 onEachLazy(settingsMenu.querySelectorAll("input"), el => {
368364 el.onblur = settingsBlurHandler;
369365 });
......@@ -377,6 +373,8 @@
377373 if (!isSettingsPage) {
378374 displaySettings();
379375 }
380 removeClass(getSettingsButton(), "rotate");
376 onEachLazy(document.querySelectorAll(".settings-menu"), settingsButton => {
377 removeClass(settingsButton, "rotate");
378 });
381379 }, 0);
382380})();
src/librustdoc/html/static/js/storage.js+37-30
......@@ -7,6 +7,7 @@
77
88/**
99 * @import * as rustdoc from "./rustdoc.d.ts";
10 * @import * as stringdex from "./stringdex.d.ts";
1011 */
1112
1213const builtinThemes = ["light", "dark", "ayu"];
......@@ -172,7 +173,7 @@ function updateLocalStorage(name, value) {
172173 } else {
173174 window.localStorage.setItem("rustdoc-" + name, value);
174175 }
175 } catch (e) {
176 } catch {
176177 // localStorage is not accessible, do nothing
177178 }
178179}
......@@ -189,7 +190,7 @@ function updateLocalStorage(name, value) {
189190function getCurrentValue(name) {
190191 try {
191192 return window.localStorage.getItem("rustdoc-" + name);
192 } catch (e) {
193 } catch {
193194 return null;
194195 }
195196}
......@@ -375,32 +376,6 @@ window.addEventListener("pageshow", ev => {
375376// That's also why this is in storage.js and not main.js.
376377//
377378// [parser]: https://html.spec.whatwg.org/multipage/parsing.html
378class RustdocSearchElement extends HTMLElement {
379 constructor() {
380 super();
381 }
382 connectedCallback() {
383 const rootPath = getVar("root-path");
384 const currentCrate = getVar("current-crate");
385 this.innerHTML = `<nav class="sub">
386 <form class="search-form">
387 <span></span> <!-- This empty span is a hacky fix for Safari - See #93184 -->
388 <div id="sidebar-button" tabindex="-1">
389 <a href="${rootPath}${currentCrate}/all.html" title="show sidebar"></a>
390 </div>
391 <input
392 class="search-input"
393 name="search"
394 aria-label="Run search in the documentation"
395 autocomplete="off"
396 spellcheck="false"
397 placeholder="Type ‘S’ or ‘/’ to search, ‘?’ for more options…"
398 type="search">
399 </form>
400 </nav>`;
401 }
402}
403window.customElements.define("rustdoc-search", RustdocSearchElement);
404379class RustdocToolbarElement extends HTMLElement {
405380 constructor() {
406381 super();
......@@ -411,11 +386,15 @@ class RustdocToolbarElement extends HTMLElement {
411386 return;
412387 }
413388 const rootPath = getVar("root-path");
389 const currentUrl = window.location.href.split("?")[0].split("#")[0];
414390 this.innerHTML = `
415 <div id="settings-menu" tabindex="-1">
391 <div id="search-button" tabindex="-1">
392 <a href="${currentUrl}?search="><span class="label">Search</span></a>
393 </div>
394 <div class="settings-menu" tabindex="-1">
416395 <a href="${rootPath}settings.html"><span class="label">Settings</span></a>
417396 </div>
418 <div id="help-button" tabindex="-1">
397 <div class="help-menu" tabindex="-1">
419398 <a href="${rootPath}help.html"><span class="label">Help</span></a>
420399 </div>
421400 <button id="toggle-all-docs"
......@@ -424,3 +403,31 @@ class="label">Summary</span></button>`;
424403 }
425404}
426405window.customElements.define("rustdoc-toolbar", RustdocToolbarElement);
406class RustdocTopBarElement extends HTMLElement {
407 constructor() {
408 super();
409 }
410 connectedCallback() {
411 const rootPath = getVar("root-path");
412 const tmplt = document.createElement("template");
413 tmplt.innerHTML = `
414 <slot name="sidebar-menu-toggle"></slot>
415 <slot></slot>
416 <slot name="settings-menu"></slot>
417 <slot name="help-menu"></slot>
418 `;
419 const shadow = this.attachShadow({ mode: "open" });
420 shadow.appendChild(tmplt.content.cloneNode(true));
421 this.innerHTML += `
422 <button class="sidebar-menu-toggle" slot="sidebar-menu-toggle" title="show sidebar">
423 </button>
424 <div class="settings-menu" slot="settings-menu" tabindex="-1">
425 <a href="${rootPath}settings.html"><span class="label">Settings</span></a>
426 </div>
427 <div class="help-menu" slot="help-menu" tabindex="-1">
428 <a href="${rootPath}help.html"><span class="label">Help</span></a>
429 </div>
430 `;
431 }
432}
433window.customElements.define("rustdoc-topbar", RustdocTopBarElement);
src/librustdoc/html/static/js/stringdex.d.ts created+165
......@@ -0,0 +1,165 @@
1export = stringdex;
2
3declare namespace stringdex {
4 /**
5 * The client interface to Stringdex.
6 */
7 interface Database {
8 getIndex(colname: string): SearchTree|undefined;
9 getData(colname: string): DataColumn|undefined;
10 }
11 /**
12 * A search index file.
13 */
14 interface SearchTree {
15 trie(): Trie;
16 search(name: Uint8Array|string): Promise<Trie?>;
17 searchLev(name: Uint8Array|string): AsyncGenerator<Trie>;
18 }
19 /**
20 * A compressed node in the search tree.
21 *
22 * This object logically addresses two interleaved trees:
23 * a "prefix tree", and a "suffix tree". If you ask for
24 * generic matches, you get both, but if you ask for one
25 * that excludes suffix-only entries, you'll get prefixes
26 * alone.
27 */
28 interface Trie {
29 matches(): RoaringBitmap;
30 substringMatches(): AsyncGenerator<RoaringBitmap>;
31 prefixMatches(): AsyncGenerator<RoaringBitmap>;
32 keys(): Uint8Array;
33 keysExcludeSuffixOnly(): Uint8Array;
34 children(): [number, Promise<Trie>][];
35 childrenExcludeSuffixOnly(): [number, Promise<Trie>][];
36 child(id: number): Promise<Trie>?;
37 }
38 /**
39 * The client interface to Stringdex.
40 */
41 interface DataColumn {
42 isEmpty(id: number): boolean;
43 at(id: number): Promise<Uint8Array|undefined>;
44 length: number,
45 }
46 /**
47 * Callbacks for a host application and VFS backend.
48 *
49 * These functions are calleb with mostly-raw data,
50 * except the JSONP wrapper is removed. For example,
51 * a file with the contents `rr_('{"A":"B"}')` should,
52 * after being pulled in, result in the `rr_` callback
53 * being invoked.
54 *
55 * The success callbacks don't need to supply the name of
56 * the file that succeeded, but, if you want successful error
57 * reporting, you'll need to remember which files are
58 * in flight and report the filename as the first parameter.
59 */
60 interface Callbacks {
61 /**
62 * Load the root of the search database
63 * @param {string} dataString
64 */
65 rr_: function(string);
66 err_rr_: function(any);
67 /**
68 * Load a nodefile in the search tree.
69 * A node file may contain multiple nodes;
70 * each node has five fields, separated by newlines.
71 * @param {string} inputBase64
72 */
73 rn_: function(string);
74 err_rn_: function(string, any);
75 /**
76 * Load a database column partition from a string
77 * @param {string} dataString
78 */
79 rd_: function(string);
80 err_rd_: function(string, any);
81 /**
82 * Load a database column partition from base64
83 * @param {string} dataString
84 */
85 rb_: function(string);
86 err_rb_: function(string, any);
87 };
88 /**
89 * Hooks that a VFS layer must provide for stringdex to load data.
90 *
91 * When the root is loaded, the Callbacks object is provided. These
92 * functions should result in callback functions being called with
93 * the contents of the file, or in error callbacks being invoked with
94 * the failed-to-load filename.
95 */
96 interface Hooks {
97 /**
98 * The first function invoked as part of loading a search database.
99 * This function must, eventually, invoke `rr_` with the string
100 * representation of the root file (the function call wrapper,
101 * `rr_('` and `')`, must be removed).
102 *
103 * The supplied callbacks object is used to feed search data back
104 * to the search engine core. You have to store it, so that
105 * loadTreeByHash and loadDataByNameAndHash can use it.
106 *
107 * If this fails, either throw an exception, or call `err_rr_`
108 * with the error object.
109 */
110 loadRoot: function(Callbacks);
111 /**
112 * Load a subtree file from the search index.
113 *
114 * If this function succeeds, call `rn_` on the callbacks
115 * object. If it fails, call `err_rn_(hashHex, error)`.
116 *
117 * @param {string} hashHex
118 */
119 loadTreeByHash: function(string);
120 /**
121 * Load a column partition from the search database.
122 *
123 * If this function succeeds, call `rd_` or `rb_` on the callbacks
124 * object. If it fails, call `err_rd_(hashHex, error)`. or `err_rb_`.
125 * To determine which one, the wrapping function call in the js file
126 * specifies it.
127 *
128 * @param {string} columnName
129 * @param {string} hashHex
130 */
131 loadDataByNameAndHash: function(string, string);
132 };
133 class RoaringBitmap {
134 constructor(array: Uint8Array|null, start?: number);
135 static makeSingleton(number: number);
136 static everything(): RoaringBitmap;
137 static empty(): RoaringBitmap;
138 isEmpty(): boolean;
139 union(that: RoaringBitmap): RoaringBitmap;
140 intersection(that: RoaringBitmap): RoaringBitmap;
141 contains(number: number): boolean;
142 entries(): Generator<number>;
143 first(): number|null;
144 consumed_len_bytes: number;
145 };
146
147 type Stringdex = {
148 /**
149 * Initialize Stringdex with VFS hooks.
150 * Returns a database that you can use.
151 */
152 loadDatabase: function(Hooks): Promise<Database>,
153 };
154
155 const Stringdex: Stringdex;
156 const RoaringBitmap: Class<stringdex.RoaringBitmap>;
157}
158
159declare global {
160 interface Window {
161 Stringdex: stringdex.Stringdex;
162 RoaringBitmap: Class<stringdex.RoaringBitmap>;
163 StringdexOnload: Array<function(stringdex.Stringdex): any>?;
164 };
165}
\ No newline at end of file
src/librustdoc/html/static/js/stringdex.js created+3217
......@@ -0,0 +1,3217 @@
1/**
2 * @import * as stringdex from "./stringdex.d.ts"
3 */
4
5const EMPTY_UINT8 = new Uint8Array();
6
7/**
8 * @property {Uint8Array} keysAndCardinalities
9 * @property {Uint8Array[]} containers
10 */
11class RoaringBitmap {
12 /**
13 * @param {Uint8Array|null} u8array
14 * @param {number} [startingOffset]
15 */
16 constructor(u8array, startingOffset) {
17 const start = startingOffset ? startingOffset : 0;
18 let i = start;
19 /** @type {Uint8Array} */
20 this.keysAndCardinalities = EMPTY_UINT8;
21 /** @type {(RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun)[]} */
22 this.containers = [];
23 /** @type {number} */
24 this.consumed_len_bytes = 0;
25 if (u8array === null || u8array.length === i || u8array[i] === 0) {
26 return this;
27 } else if (u8array[i] > 0xf0) {
28 // Special representation of tiny sets that are close together
29 const lspecial = u8array[i] & 0x0f;
30 this.keysAndCardinalities = new Uint8Array(lspecial * 4);
31 let pspecial = i + 1;
32 let key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8);
33 let value = u8array[pspecial] | (u8array[pspecial + 1] << 8);
34 let entry = (key << 16) | value;
35 let container;
36 container = new RoaringBitmapArray(1, new Uint8Array(4));
37 container.array[0] = value & 0xFF;
38 container.array[1] = (value >> 8) & 0xFF;
39 this.containers.push(container);
40 this.keysAndCardinalities[0] = key;
41 this.keysAndCardinalities[1] = key >> 8;
42 pspecial += 4;
43 for (let ispecial = 1; ispecial < lspecial; ispecial += 1) {
44 entry += u8array[pspecial] | (u8array[pspecial + 1] << 8);
45 value = entry & 0xffff;
46 key = entry >> 16;
47 container = this.addToArrayAt(key);
48 const cardinalityOld = container.cardinality;
49 container.array[cardinalityOld * 2] = value & 0xFF;
50 container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF;
51 container.cardinality = cardinalityOld + 1;
52 pspecial += 2;
53 }
54 this.consumed_len_bytes = pspecial - i;
55 return this;
56 } else if (u8array[i] < 0x3a) {
57 // Special representation of tiny sets with arbitrary 32-bit integers
58 const lspecial = u8array[i];
59 this.keysAndCardinalities = new Uint8Array(lspecial * 4);
60 let pspecial = i + 1;
61 for (let ispecial = 0; ispecial < lspecial; ispecial += 1) {
62 const key = u8array[pspecial + 2] | (u8array[pspecial + 3] << 8);
63 const value = u8array[pspecial] | (u8array[pspecial + 1] << 8);
64 const container = this.addToArrayAt(key);
65 const cardinalityOld = container.cardinality;
66 container.array[cardinalityOld * 2] = value & 0xFF;
67 container.array[(cardinalityOld * 2) + 1] = (value >> 8) & 0xFF;
68 container.cardinality = cardinalityOld + 1;
69 pspecial += 4;
70 }
71 this.consumed_len_bytes = pspecial - i;
72 return this;
73 }
74 // https://github.com/RoaringBitmap/RoaringFormatSpec
75 //
76 // Roaring bitmaps are used for flags that can be kept in their
77 // compressed form, even when loaded into memory. This decoder
78 // turns the containers into objects, but uses byte array
79 // slices of the original format for the data payload.
80 const has_runs = u8array[i] === 0x3b;
81 if (u8array[i] !== 0x3a && u8array[i] !== 0x3b) {
82 throw new Error("not a roaring bitmap: " + u8array[i]);
83 }
84 const size = has_runs ?
85 ((u8array[i + 2] | (u8array[i + 3] << 8)) + 1) :
86 ((u8array[i + 4] | (u8array[i + 5] << 8) |
87 (u8array[i + 6] << 16) | (u8array[i + 7] << 24)));
88 i += has_runs ? 4 : 8;
89 let is_run;
90 if (has_runs) {
91 const is_run_len = (size + 7) >> 3;
92 is_run = new Uint8Array(u8array.buffer, i + u8array.byteOffset, is_run_len);
93 i += is_run_len;
94 } else {
95 is_run = EMPTY_UINT8;
96 }
97 this.keysAndCardinalities = u8array.subarray(i, i + (size * 4));
98 i += size * 4;
99 let offsets = null;
100 if (!has_runs || size >= 4) {
101 offsets = [];
102 for (let j = 0; j < size; ++j) {
103 offsets.push(u8array[i] | (u8array[i + 1] << 8) | (u8array[i + 2] << 16) |
104 (u8array[i + 3] << 24));
105 i += 4;
106 }
107 }
108 for (let j = 0; j < size; ++j) {
109 if (offsets && offsets[j] !== i - start) {
110 throw new Error(`corrupt bitmap ${j}: ${i - start} / ${offsets[j]}`);
111 }
112 const cardinality = (this.keysAndCardinalities[(j * 4) + 2] |
113 (this.keysAndCardinalities[(j * 4) + 3] << 8)) + 1;
114 if (is_run[j >> 3] & (1 << (j & 0x7))) {
115 const runcount = (u8array[i] | (u8array[i + 1] << 8));
116 i += 2;
117 this.containers.push(new RoaringBitmapRun(
118 runcount,
119 new Uint8Array(u8array.buffer, i + u8array.byteOffset, runcount * 4),
120 ));
121 i += runcount * 4;
122 } else if (cardinality >= 4096) {
123 this.containers.push(new RoaringBitmapBits(new Uint8Array(
124 u8array.buffer,
125 i + u8array.byteOffset, 8192,
126 )));
127 i += 8192;
128 } else {
129 const end = cardinality * 2;
130 this.containers.push(new RoaringBitmapArray(
131 cardinality,
132 new Uint8Array(u8array.buffer, i + u8array.byteOffset, end),
133 ));
134 i += end;
135 }
136 }
137 this.consumed_len_bytes = i - start;
138 }
139 /**
140 * @param {number} number
141 * @returns {RoaringBitmap}
142 */
143 static makeSingleton(number) {
144 const result = new RoaringBitmap(null, 0);
145 result.keysAndCardinalities = Uint8Array.of(
146 (number >> 16), (number >> 24),
147 0, 0, // keysAndCardinalities stores the true cardinality minus 1
148 );
149 result.containers.push(new RoaringBitmapArray(
150 1,
151 Uint8Array.of(number, number >> 8),
152 ));
153 return result;
154 }
155 /** @returns {RoaringBitmap} */
156 static everything() {
157 if (EVERYTHING_BITMAP.isEmpty()) {
158 let i = 0;
159 const l = 1 << 16;
160 const everything_range = new RoaringBitmapRun(1, Uint8Array.of(0, 0, 0xff, 0xff));
161 EVERYTHING_BITMAP.keysAndCardinalities = new Uint8Array(l * 4);
162 while (i < l) {
163 EVERYTHING_BITMAP.containers.push(everything_range);
164 // key
165 EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 0] = i;
166 EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 1] = i >> 8;
167 // cardinality (minus one)
168 EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 2] = 0xff;
169 EVERYTHING_BITMAP.keysAndCardinalities[(i * 4) + 3] = 0xff;
170 i += 1;
171 }
172 }
173 return EVERYTHING_BITMAP;
174 }
175 /** @returns {RoaringBitmap} */
176 static empty() {
177 return EMPTY_BITMAP;
178 }
179 /** @returns {boolean} */
180 isEmpty() {
181 return this.containers.length === 0;
182 }
183 /**
184 * Helper function used when constructing bitmaps from lists.
185 * Returns an array container with at least two free byte slots
186 * and bumps `this.cardinalities`.
187 * @param {number} key
188 * @returns {RoaringBitmapArray}
189 */
190 addToArrayAt(key) {
191 let mid = this.getContainerId(key);
192 /** @type {RoaringBitmapArray|RoaringBitmapBits|RoaringBitmapRun} */
193 let container;
194 if (mid === -1) {
195 container = new RoaringBitmapArray(0, new Uint8Array(2));
196 mid = this.containers.length;
197 this.containers.push(container);
198 if (mid * 4 > this.keysAndCardinalities.length) {
199 const keysAndContainers = new Uint8Array(mid * 8);
200 keysAndContainers.set(this.keysAndCardinalities);
201 this.keysAndCardinalities = keysAndContainers;
202 }
203 this.keysAndCardinalities[(mid * 4) + 0] = key;
204 this.keysAndCardinalities[(mid * 4) + 1] = key >> 8;
205 } else {
206 container = this.containers[mid];
207 const cardinalityOld =
208 this.keysAndCardinalities[(mid * 4) + 2] |
209 (this.keysAndCardinalities[(mid * 4) + 3] << 8);
210 const cardinality = cardinalityOld + 1;
211 this.keysAndCardinalities[(mid * 4) + 2] = cardinality;
212 this.keysAndCardinalities[(mid * 4) + 3] = cardinality >> 8;
213 }
214 // the logic for handing this number is annoying, because keysAndCardinalities stores
215 // the cardinality *minus one*, so that it can count up to 65536 with only two bytes
216 // (because empty containers are never stored).
217 //
218 // So, if this is a new container, the stored cardinality contains `0 0`, which is
219 // the proper value of the old cardinality (an imaginary empty container existed).
220 // If this is adding to an existing container, then the above `else` branch bumps it
221 // by one, leaving us with a proper value of `cardinality - 1`.
222 const cardinalityOld =
223 this.keysAndCardinalities[(mid * 4) + 2] |
224 (this.keysAndCardinalities[(mid * 4) + 3] << 8);
225 if (!(container instanceof RoaringBitmapArray) ||
226 container.array.byteLength < ((cardinalityOld + 1) * 2)
227 ) {
228 const newBuf = new Uint8Array((cardinalityOld + 1) * 4);
229 let idx = 0;
230 for (const cvalue of container.values()) {
231 newBuf[idx] = cvalue & 0xFF;
232 newBuf[idx + 1] = (cvalue >> 8) & 0xFF;
233 idx += 2;
234 }
235 if (container instanceof RoaringBitmapArray) {
236 container.cardinality = cardinalityOld;
237 container.array = newBuf;
238 return container;
239 }
240 const newcontainer = new RoaringBitmapArray(cardinalityOld, newBuf);
241 this.containers[mid] = newcontainer;
242 return newcontainer;
243 } else {
244 return container;
245 }
246 }
247 /**
248 * @param {RoaringBitmap} that
249 * @returns {RoaringBitmap}
250 */
251 union(that) {
252 if (this.isEmpty()) {
253 return that;
254 }
255 if (that.isEmpty()) {
256 return this;
257 }
258 if (this === RoaringBitmap.everything() || that === RoaringBitmap.everything()) {
259 return RoaringBitmap.everything();
260 }
261 let i = 0;
262 const il = this.containers.length;
263 let j = 0;
264 const jl = that.containers.length;
265 const result = new RoaringBitmap(null, 0);
266 result.keysAndCardinalities = new Uint8Array((il + jl) * 4);
267 while (i < il || j < jl) {
268 const ik = i * 4;
269 const jk = j * 4;
270 const k = result.containers.length * 4;
271 if (j >= jl || (i < il && (
272 (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) ||
273 (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] &&
274 this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk])
275 ))) {
276 result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
277 result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
278 result.keysAndCardinalities[k + 2] = this.keysAndCardinalities[ik + 2];
279 result.keysAndCardinalities[k + 3] = this.keysAndCardinalities[ik + 3];
280 result.containers.push(this.containers[i]);
281 i += 1;
282 } else if (i >= il || (j < jl && (
283 (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) ||
284 (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] &&
285 that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik])
286 ))) {
287 result.keysAndCardinalities[k + 0] = that.keysAndCardinalities[jk + 0];
288 result.keysAndCardinalities[k + 1] = that.keysAndCardinalities[jk + 1];
289 result.keysAndCardinalities[k + 2] = that.keysAndCardinalities[jk + 2];
290 result.keysAndCardinalities[k + 3] = that.keysAndCardinalities[jk + 3];
291 result.containers.push(that.containers[j]);
292 j += 1;
293 } else {
294 // this key is not smaller than that key
295 // that key is not smaller than this key
296 // they must be equal
297 const thisContainer = this.containers[i];
298 const thatContainer = that.containers[j];
299 let card = 0;
300 if (thisContainer instanceof RoaringBitmapBits &&
301 thatContainer instanceof RoaringBitmapBits
302 ) {
303 const resultArray = new Uint8Array(
304 thisContainer.array.length > thatContainer.array.length ?
305 thisContainer.array.length :
306 thatContainer.array.length,
307 );
308 let k = 0;
309 const kl = resultArray.length;
310 while (k < kl) {
311 const c = thisContainer.array[k] | thatContainer.array[k];
312 resultArray[k] = c;
313 card += bitCount(c);
314 k += 1;
315 }
316 result.containers.push(new RoaringBitmapBits(resultArray));
317 } else {
318 const thisValues = thisContainer.values();
319 const thatValues = thatContainer.values();
320 let thisResult = thisValues.next();
321 let thatResult = thatValues.next();
322 /** @type {Array<number>} */
323 const resultValues = [];
324 while (!thatResult.done || !thisResult.done) {
325 // generator will definitely implement the iterator protocol correctly
326 /** @type {number} */
327 const thisValue = thisResult.value;
328 /** @type {number} */
329 const thatValue = thatResult.value;
330 if (thatResult.done || thisValue < thatValue) {
331 resultValues.push(thisValue);
332 thisResult = thisValues.next();
333 } else if (thisResult.done || thatValue < thisValue) {
334 resultValues.push(thatValue);
335 thatResult = thatValues.next();
336 } else {
337 // this value is not smaller than that value
338 // that value is not smaller than this value
339 // they must be equal
340 resultValues.push(thisValue);
341 thisResult = thisValues.next();
342 thatResult = thatValues.next();
343 }
344 }
345 const resultArray = new Uint8Array(resultValues.length * 2);
346 let k = 0;
347 for (const value of resultValues) {
348 // roaring bitmap is little endian
349 resultArray[k] = value & 0xFF;
350 resultArray[k + 1] = (value >> 8) & 0xFF;
351 k += 2;
352 }
353 result.containers.push(new RoaringBitmapArray(
354 resultValues.length,
355 resultArray,
356 ));
357 card = resultValues.length;
358 }
359 result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
360 result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
361 card -= 1;
362 result.keysAndCardinalities[k + 2] = card;
363 result.keysAndCardinalities[k + 3] = card >> 8;
364 i += 1;
365 j += 1;
366 }
367 }
368 return result;
369 }
370 /**
371 * @param {RoaringBitmap} that
372 * @returns {RoaringBitmap}
373 */
374 intersection(that) {
375 if (this.isEmpty() || that.isEmpty()) {
376 return EMPTY_BITMAP;
377 }
378 if (this === RoaringBitmap.everything()) {
379 return that;
380 }
381 if (that === RoaringBitmap.everything()) {
382 return this;
383 }
384 let i = 0;
385 const il = this.containers.length;
386 let j = 0;
387 const jl = that.containers.length;
388 const result = new RoaringBitmap(null, 0);
389 result.keysAndCardinalities = new Uint8Array((il > jl ? il : jl) * 4);
390 while (i < il && j < jl) {
391 const ik = i * 4;
392 const jk = j * 4;
393 const k = result.containers.length * 4;
394 if (j >= jl || (i < il && (
395 (this.keysAndCardinalities[ik + 1] < that.keysAndCardinalities[jk + 1]) ||
396 (this.keysAndCardinalities[ik + 1] === that.keysAndCardinalities[jk + 1] &&
397 this.keysAndCardinalities[ik] < that.keysAndCardinalities[jk])
398 ))) {
399 i += 1;
400 } else if (i >= il || (j < jl && (
401 (that.keysAndCardinalities[jk + 1] < this.keysAndCardinalities[ik + 1]) ||
402 (that.keysAndCardinalities[jk + 1] === this.keysAndCardinalities[ik + 1] &&
403 that.keysAndCardinalities[jk] < this.keysAndCardinalities[ik])
404 ))) {
405 j += 1;
406 } else {
407 // this key is not smaller than that key
408 // that key is not smaller than this key
409 // they must be equal
410 const thisContainer = this.containers[i];
411 const thatContainer = that.containers[j];
412 let card = 0;
413 if (thisContainer instanceof RoaringBitmapBits &&
414 thatContainer instanceof RoaringBitmapBits
415 ) {
416 const resultArray = new Uint8Array(
417 thisContainer.array.length > thatContainer.array.length ?
418 thisContainer.array.length :
419 thatContainer.array.length,
420 );
421 let k = 0;
422 const kl = resultArray.length;
423 while (k < kl) {
424 const c = thisContainer.array[k] & thatContainer.array[k];
425 resultArray[k] = c;
426 card += bitCount(c);
427 k += 1;
428 }
429 if (card !== 0) {
430 result.containers.push(new RoaringBitmapBits(resultArray));
431 }
432 } else {
433 const thisValues = thisContainer.values();
434 const thatValues = thatContainer.values();
435 let thisValue = thisValues.next();
436 let thatValue = thatValues.next();
437 const resultValues = [];
438 while (!thatValue.done && !thisValue.done) {
439 if (thisValue.value < thatValue.value) {
440 thisValue = thisValues.next();
441 } else if (thatValue.value < thisValue.value) {
442 thatValue = thatValues.next();
443 } else {
444 // this value is not smaller than that value
445 // that value is not smaller than this value
446 // they must be equal
447 resultValues.push(thisValue.value);
448 thisValue = thisValues.next();
449 thatValue = thatValues.next();
450 }
451 }
452 card = resultValues.length;
453 if (card !== 0) {
454 const resultArray = new Uint8Array(resultValues.length * 2);
455 let k = 0;
456 for (const value of resultValues) {
457 // roaring bitmap is little endian
458 resultArray[k] = value & 0xFF;
459 resultArray[k + 1] = (value >> 8) & 0xFF;
460 k += 2;
461 }
462 result.containers.push(new RoaringBitmapArray(
463 resultValues.length,
464 resultArray,
465 ));
466 }
467 }
468 if (card !== 0) {
469 result.keysAndCardinalities[k + 0] = this.keysAndCardinalities[ik + 0];
470 result.keysAndCardinalities[k + 1] = this.keysAndCardinalities[ik + 1];
471 card -= 1;
472 result.keysAndCardinalities[k + 2] = card;
473 result.keysAndCardinalities[k + 3] = card >> 8;
474 }
475 i += 1;
476 j += 1;
477 }
478 }
479 return result;
480 }
481 /** @param {number} keyvalue */
482 contains(keyvalue) {
483 const key = keyvalue >> 16;
484 const value = keyvalue & 0xFFFF;
485 const mid = this.getContainerId(key);
486 return mid === -1 ? false : this.containers[mid].contains(value);
487 }
488 /**
489 * @param {number} key
490 * @returns {number}
491 */
492 getContainerId(key) {
493 // Binary search algorithm copied from
494 // https://en.wikipedia.org/wiki/Binary_search#Procedure
495 //
496 // Format is required by specification to be sorted.
497 // Because keys are 16 bits and unique, length can't be
498 // bigger than 2**16, and because we have 32 bits of safe int,
499 // left + right can't overflow.
500 let left = 0;
501 let right = this.containers.length - 1;
502 while (left <= right) {
503 const mid = Math.floor((left + right) / 2);
504 const x = this.keysAndCardinalities[(mid * 4)] |
505 (this.keysAndCardinalities[(mid * 4) + 1] << 8);
506 if (x < key) {
507 left = mid + 1;
508 } else if (x > key) {
509 right = mid - 1;
510 } else {
511 return mid;
512 }
513 }
514 return -1;
515 }
516 * entries() {
517 const l = this.containers.length;
518 for (let i = 0; i < l; ++i) {
519 const key = this.keysAndCardinalities[i * 4] |
520 (this.keysAndCardinalities[(i * 4) + 1] << 8);
521 for (const value of this.containers[i].values()) {
522 yield (key << 16) | value;
523 }
524 }
525 }
526 /**
527 * @returns {number|null}
528 */
529 first() {
530 for (const entry of this.entries()) {
531 return entry;
532 }
533 return null;
534 }
535 /**
536 * @returns {number}
537 */
538 cardinality() {
539 let result = 0;
540 const l = this.containers.length;
541 for (let i = 0; i < l; ++i) {
542 const card = this.keysAndCardinalities[(i * 4) + 2] |
543 (this.keysAndCardinalities[(i * 4) + 3] << 8);
544 result += card + 1;
545 }
546 return result;
547 }
548}
549
550class RoaringBitmapRun {
551 /**
552 * @param {number} runcount
553 * @param {Uint8Array} array
554 */
555 constructor(runcount, array) {
556 this.runcount = runcount;
557 this.array = array;
558 }
559 /** @param {number} value */
560 contains(value) {
561 // Binary search algorithm copied from
562 // https://en.wikipedia.org/wiki/Binary_search#Procedure
563 //
564 // Since runcount is stored as 16 bits, left + right
565 // can't overflow.
566 let left = 0;
567 let right = this.runcount - 1;
568 while (left <= right) {
569 const mid = (left + right) >> 1;
570 const i = mid * 4;
571 const start = this.array[i] | (this.array[i + 1] << 8);
572 const lenm1 = this.array[i + 2] | (this.array[i + 3] << 8);
573 if ((start + lenm1) < value) {
574 left = mid + 1;
575 } else if (start > value) {
576 right = mid - 1;
577 } else {
578 return true;
579 }
580 }
581 return false;
582 }
583 * values() {
584 let i = 0;
585 while (i < this.runcount) {
586 const start = this.array[i * 4] | (this.array[(i * 4) + 1] << 8);
587 const lenm1 = this.array[(i * 4) + 2] | (this.array[(i * 4) + 3] << 8);
588 let value = start;
589 let j = 0;
590 while (j <= lenm1) {
591 yield value;
592 value += 1;
593 j += 1;
594 }
595 i += 1;
596 }
597 }
598}
599class RoaringBitmapArray {
600 /**
601 * @param {number} cardinality
602 * @param {Uint8Array} array
603 */
604 constructor(cardinality, array) {
605 this.cardinality = cardinality;
606 this.array = array;
607 }
608 /** @param {number} value */
609 contains(value) {
610 // Binary search algorithm copied from
611 // https://en.wikipedia.org/wiki/Binary_search#Procedure
612 //
613 // Since cardinality can't be higher than 4096, left + right
614 // cannot overflow.
615 let left = 0;
616 let right = this.cardinality - 1;
617 while (left <= right) {
618 const mid = (left + right) >> 1;
619 const i = mid * 2;
620 const x = this.array[i] | (this.array[i + 1] << 8);
621 if (x < value) {
622 left = mid + 1;
623 } else if (x > value) {
624 right = mid - 1;
625 } else {
626 return true;
627 }
628 }
629 return false;
630 }
631 /** @returns {Generator<number>} */
632 * values() {
633 let i = 0;
634 const l = this.cardinality * 2;
635 while (i < l) {
636 yield this.array[i] | (this.array[i + 1] << 8);
637 i += 2;
638 }
639 }
640}
641class RoaringBitmapBits {
642 /**
643 * @param {Uint8Array} array
644 */
645 constructor(array) {
646 this.array = array;
647 }
648 /** @param {number} value */
649 contains(value) {
650 return !!(this.array[value >> 3] & (1 << (value & 7)));
651 }
652 * values() {
653 let i = 0;
654 const l = this.array.length << 3;
655 while (i < l) {
656 if (this.contains(i)) {
657 yield i;
658 }
659 i += 1;
660 }
661 }
662}
663
664const EMPTY_BITMAP = new RoaringBitmap(null, 0);
665EMPTY_BITMAP.consumed_len_bytes = 0;
666const EMPTY_BITMAP1 = new RoaringBitmap(null, 0);
667EMPTY_BITMAP1.consumed_len_bytes = 1;
668const EVERYTHING_BITMAP = new RoaringBitmap(null, 0);
669
670/**
671 * A mapping from six byte nodeids to an arbitrary value.
672 * We don't just use `Map` because that requires double hashing.
673 * @template T
674 * @property {Uint8Array} keys
675 * @property {T[]} values
676 * @property {number} size
677 * @property {number} capacityClass
678 */
679class HashTable {
680 /**
681 * Construct an empty hash table.
682 */
683 constructor() {
684 this.keys = EMPTY_UINT8;
685 /** @type {(T|undefined)[]} */
686 this.values = [];
687 this.size = 0;
688 this.capacityClass = 0;
689 }
690 /**
691 * @returns {Generator<[Uint8Array, T]>}
692 */
693 * entries() {
694 const keys = this.keys;
695 const values = this.values;
696 const l = this.values.length;
697 for (let i = 0; i < l; i += 1) {
698 const value = values[i];
699 if (value !== undefined) {
700 yield [keys.subarray(i * 6, (i + 1) * 6), value];
701 }
702 }
703 }
704 /**
705 * Add a value to the hash table.
706 * @param {Uint8Array} key
707 * @param {T} value
708 */
709 set(key, value) {
710 // 90 % load factor
711 if (this.size * 10 >= this.values.length * 9) {
712 const keys = this.keys;
713 const values = this.values;
714 const l = values.length;
715 this.capacityClass += 1;
716 const capacity = 1 << this.capacityClass;
717 this.keys = new Uint8Array(capacity * 6);
718 this.values = [];
719 for (let i = 0; i < capacity; i += 1) {
720 this.values.push(undefined);
721 }
722 this.size = 0;
723 for (let i = 0; i < l; i += 1) {
724 const oldValue = values[i];
725 if (oldValue !== undefined) {
726 this.setNoGrow(keys, i * 6, oldValue);
727 }
728 }
729 }
730 this.setNoGrow(key, 0, value);
731 }
732 /**
733 * @param {Uint8Array} key
734 * @param {number} start
735 * @param {T} value
736 */
737 setNoGrow(key, start, value) {
738 const mask = ~(0xffffffff << this.capacityClass);
739 const keys = this.keys;
740 const values = this.values;
741 const l = 1 << this.capacityClass;
742 // because we know that our values are already hashed,
743 // just chop off the lower four bytes
744 let slot = (
745 (key[start + 2] << 24) |
746 (key[start + 3] << 16) |
747 (key[start + 4] << 8) |
748 key[start + 5]
749 ) & mask;
750 for (let distance = 0; distance < l; ) {
751 const j = slot * 6;
752 const otherValue = values[slot];
753 if (otherValue === undefined) {
754 values[slot] = value;
755 const keysStart = slot * 6;
756 keys[keysStart + 0] = key[start + 0];
757 keys[keysStart + 1] = key[start + 1];
758 keys[keysStart + 2] = key[start + 2];
759 keys[keysStart + 3] = key[start + 3];
760 keys[keysStart + 4] = key[start + 4];
761 keys[keysStart + 5] = key[start + 5];
762 this.size += 1;
763 break;
764 } else if (
765 key[start + 0] === keys[j + 0] &&
766 key[start + 1] === keys[j + 1] &&
767 key[start + 2] === keys[j + 2] &&
768 key[start + 3] === keys[j + 3] &&
769 key[start + 4] === keys[j + 4] &&
770 key[start + 5] === keys[j + 5]
771 ) {
772 values[slot] = value;
773 break;
774 } else {
775 const otherPreferredSlot = (
776 (keys[j + 2] << 24) | (keys[j + 3] << 16) |
777 (keys[j + 4] << 8) | keys[j + 5]
778 ) & mask;
779 const otherDistance = otherPreferredSlot <= slot ?
780 slot - otherPreferredSlot :
781 (l - otherPreferredSlot) + slot;
782 if (distance > otherDistance) {
783 // if the other key is closer to its preferred slot than this one,
784 // then insert our node in its place and swap
785 //
786 // https://cglab.ca/~abeinges/blah/robinhood-part-1/
787 const otherKey = keys.slice(j, j + 6);
788 values[slot] = value;
789 value = otherValue;
790 keys[j + 0] = key[start + 0];
791 keys[j + 1] = key[start + 1];
792 keys[j + 2] = key[start + 2];
793 keys[j + 3] = key[start + 3];
794 keys[j + 4] = key[start + 4];
795 keys[j + 5] = key[start + 5];
796 key = otherKey;
797 start = 0;
798 distance = otherDistance;
799 }
800 distance += 1;
801 slot = (slot + 1) & mask;
802 }
803 }
804 }
805 /**
806 * Retrieve a value
807 * @param {Uint8Array} key
808 * @returns {T|undefined}
809 */
810 get(key) {
811 if (key.length !== 6) {
812 throw "invalid key";
813 }
814 return this.getWithOffsetKey(key, 0);
815 }
816 /**
817 * Retrieve a value
818 * @param {Uint8Array} key
819 * @param {number} start
820 * @returns {T|undefined}
821 */
822 getWithOffsetKey(key, start) {
823 const mask = ~(0xffffffff << this.capacityClass);
824 const keys = this.keys;
825 const values = this.values;
826 const l = 1 << this.capacityClass;
827 // because we know that our values are already hashed,
828 // just chop off the lower four bytes
829 let slot = (
830 (key[start + 2] << 24) |
831 (key[start + 3] << 16) |
832 (key[start + 4] << 8) |
833 key[start + 5]
834 ) & mask;
835 for (let distance = 0; distance < l; distance += 1) {
836 const j = slot * 6;
837 const value = values[slot];
838 if (value === undefined) {
839 break;
840 } else if (
841 key[start + 0] === keys[j + 0] &&
842 key[start + 1] === keys[j + 1] &&
843 key[start + 2] === keys[j + 2] &&
844 key[start + 3] === keys[j + 3] &&
845 key[start + 4] === keys[j + 4] &&
846 key[start + 5] === keys[j + 5]
847 ) {
848 return value;
849 } else {
850 const otherPreferredSlot = (
851 (keys[j + 2] << 24) | (keys[j + 3] << 16) |
852 (keys[j + 4] << 8) | keys[j + 5]
853 ) & mask;
854 const otherDistance = otherPreferredSlot <= slot ?
855 slot - otherPreferredSlot :
856 (l - otherPreferredSlot) + slot;
857 if (distance > otherDistance) {
858 break;
859 }
860 }
861 slot = (slot + 1) & mask;
862 }
863 return undefined;
864 }
865}
866
867/*eslint-disable */
868// ignore-tidy-linelength
869/** <https://stackoverflow.com/questions/43122082/efficiently-count-the-number-of-bits-in-an-integer-in-javascript>
870 * @param {number} n
871 * @returns {number}
872 */
873function bitCount(n) {
874 n = (~~n) - ((n >> 1) & 0x55555555);
875 n = (n & 0x33333333) + ((n >> 2) & 0x33333333);
876 return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;
877}
878/*eslint-enable */
879
880/**
881 * @param {stringdex.Hooks} hooks
882 * @returns {Promise<stringdex.Database>}
883 */
884function loadDatabase(hooks) {
885 /** @type {stringdex.Callbacks} */
886 const callbacks = {
887 rr_: function(data) {
888 const dataObj = JSON.parse(data);
889 for (const colName of Object.keys(dataObj)) {
890 if (Object.hasOwn(dataObj[colName], "I")) {
891 registry.searchTreeRoots.set(
892 colName,
893 makeSearchTreeFromBase64(dataObj[colName].I)[1],
894 );
895 }
896 if (Object.hasOwn(dataObj[colName], "N")) {
897 const counts = [];
898 const countsstring = dataObj[colName]["N"];
899 let i = 0;
900 const l = countsstring.length;
901 while (i < l) {
902 let n = 0;
903 let c = countsstring.charCodeAt(i);
904 while (c < 96) { // 96 = "`"
905 n = (n << 4) | (c & 0xF);
906 i += 1;
907 c = countsstring.charCodeAt(i);
908 }
909 n = (n << 4) | (c & 0xF);
910 counts.push(n);
911 i += 1;
912 }
913 registry.dataColumns.set(colName, new DataColumn(
914 counts,
915 makeUint8ArrayFromBase64(dataObj[colName]["H"]),
916 new RoaringBitmap(makeUint8ArrayFromBase64(dataObj[colName]["E"]), 0),
917 colName,
918 ));
919 }
920 }
921 const cb = registry.searchTreeRootCallback;
922 if (cb) {
923 cb(null, new Database(registry.searchTreeRoots, registry.dataColumns));
924 }
925 },
926 err_rr_: function(err) {
927 const cb = registry.searchTreeRootCallback;
928 if (cb) {
929 cb(err, null);
930 }
931 },
932 rd_: function(dataString) {
933 const l = dataString.length;
934 const data = new Uint8Array(l);
935 for (let i = 0; i < l; ++i) {
936 data[i] = dataString.charCodeAt(i);
937 }
938 loadColumnFromBytes(data);
939 },
940 err_rd_: function(filename, err) {
941 const nodeid = makeUint8ArrayFromHex(filename);
942 const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid);
943 if (cb) {
944 cb(err, null);
945 }
946 },
947 rb_: function(dataString64) {
948 loadColumnFromBytes(makeUint8ArrayFromBase64(dataString64));
949 },
950 err_rb_: function(filename, err) {
951 const nodeid = makeUint8ArrayFromHex(filename);
952 const cb = registry.dataColumnLoadPromiseCallbacks.get(nodeid);
953 if (cb) {
954 cb(err, null);
955 }
956 },
957 rn_: function(inputBase64) {
958 const [nodeid, tree] = makeSearchTreeFromBase64(inputBase64);
959 const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
960 if (cb) {
961 cb(null, tree);
962 registry.searchTreeLoadPromiseCallbacks.set(nodeid, null);
963 }
964 },
965 err_rn_: function(filename, err) {
966 const nodeid = makeUint8ArrayFromHex(filename);
967 const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
968 if (cb) {
969 cb(err, null);
970 }
971 },
972 };
973
974 /**
975 * @type {{
976 * searchTreeRoots: Map<string, SearchTree>;
977 * searchTreeLoadPromiseCallbacks: HashTable<(function(any, SearchTree?): any)|null>;
978 * searchTreePromises: HashTable<Promise<SearchTree>>;
979 * dataColumnLoadPromiseCallbacks: HashTable<function(any, Uint8Array[]?): any>;
980 * dataColumns: Map<string, DataColumn>;
981 * dataColumnsBuckets: Map<string, HashTable<Promise<Uint8Array[]>>>;
982 * searchTreeLoadByNodeID: function(Uint8Array): Promise<SearchTree>;
983 * searchTreeRootCallback?: function(any, Database?): any;
984 * dataLoadByNameAndHash: function(string, Uint8Array): Promise<Uint8Array[]>;
985 * }}
986 */
987 const registry = {
988 searchTreeRoots: new Map(),
989 searchTreeLoadPromiseCallbacks: new HashTable(),
990 searchTreePromises: new HashTable(),
991 dataColumnLoadPromiseCallbacks: new HashTable(),
992 dataColumns: new Map(),
993 dataColumnsBuckets: new Map(),
994 searchTreeLoadByNodeID: function(nodeid) {
995 const existingPromise = registry.searchTreePromises.get(nodeid);
996 if (existingPromise) {
997 return existingPromise;
998 }
999 /** @type {Promise<SearchTree>} */
1000 let newPromise;
1001 if ((nodeid[0] & 0x80) !== 0) {
1002 const isWhole = (nodeid[0] & 0x40) !== 0;
1003 let leaves;
1004 if ((nodeid[0] & 0x10) !== 0) {
1005 let id1 = (nodeid[2] << 8) | nodeid[3];
1006 if ((nodeid[0] & 0x20) !== 0) {
1007 // when data is present, id1 can be up to 20 bits
1008 id1 |= ((nodeid[1] & 0x0f) << 16);
1009 } else {
1010 // otherwise, we fit in 28
1011 id1 |= ((nodeid[0] & 0x0f) << 24) | (nodeid[1] << 16);
1012 }
1013 const id2 = id1 + ((nodeid[4] << 8) | nodeid[5]);
1014 leaves = RoaringBitmap.makeSingleton(id1)
1015 .union(RoaringBitmap.makeSingleton(id2));
1016 } else {
1017 leaves = RoaringBitmap.makeSingleton(
1018 (nodeid[2] << 24) | (nodeid[3] << 16) |
1019 (nodeid[4] << 8) | nodeid[5],
1020 );
1021 }
1022 const data = (nodeid[0] & 0x20) !== 0 ?
1023 Uint8Array.of(((nodeid[0] & 0x0f) << 4) | (nodeid[1] >> 4)) :
1024 EMPTY_UINT8;
1025 newPromise = Promise.resolve(new SearchTree(
1026 EMPTY_SEARCH_TREE_BRANCHES,
1027 EMPTY_SEARCH_TREE_BRANCHES,
1028 data,
1029 isWhole ? leaves : EMPTY_BITMAP,
1030 isWhole ? EMPTY_BITMAP : leaves,
1031 ));
1032 } else {
1033 const hashHex = makeHexFromUint8Array(nodeid);
1034 newPromise = new Promise((resolve, reject) => {
1035 const cb = registry.searchTreeLoadPromiseCallbacks.get(nodeid);
1036 if (cb) {
1037 registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => {
1038 cb(err, data);
1039 if (data) {
1040 resolve(data);
1041 } else {
1042 reject(err);
1043 }
1044 });
1045 } else {
1046 registry.searchTreeLoadPromiseCallbacks.set(nodeid, (err, data) => {
1047 if (data) {
1048 resolve(data);
1049 } else {
1050 reject(err);
1051 }
1052 });
1053 hooks.loadTreeByHash(hashHex);
1054 }
1055 });
1056 }
1057 registry.searchTreePromises.set(nodeid, newPromise);
1058 return newPromise;
1059 },
1060 dataLoadByNameAndHash: function(name, hash) {
1061 let dataColumnBuckets = registry.dataColumnsBuckets.get(name);
1062 if (dataColumnBuckets === undefined) {
1063 dataColumnBuckets = new HashTable();
1064 registry.dataColumnsBuckets.set(name, dataColumnBuckets);
1065 }
1066 const existingBucket = dataColumnBuckets.get(hash);
1067 if (existingBucket) {
1068 return existingBucket;
1069 }
1070 const hashHex = makeHexFromUint8Array(hash);
1071 /** @type {Promise<Uint8Array[]>} */
1072 const newBucket = new Promise((resolve, reject) => {
1073 const cb = registry.dataColumnLoadPromiseCallbacks.get(hash);
1074 if (cb) {
1075 registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => {
1076 cb(err, data);
1077 if (data) {
1078 resolve(data);
1079 } else {
1080 reject(err);
1081 }
1082 });
1083 } else {
1084 registry.dataColumnLoadPromiseCallbacks.set(hash, (err, data) => {
1085 if (data) {
1086 resolve(data);
1087 } else {
1088 reject(err);
1089 }
1090 });
1091 hooks.loadDataByNameAndHash(name, hashHex);
1092 }
1093 });
1094 dataColumnBuckets.set(hash, newBucket);
1095 return newBucket;
1096 },
1097 };
1098
1099 /**
1100 * The set of child subtrees.
1101 * @type {{
1102 * nodeids: Uint8Array,
1103 * subtrees: Array<Promise<SearchTree>|null>,
1104 * }}
1105 */
1106 class SearchTreeBranches {
1107 /**
1108 * Construct the subtree list with `length` nulls
1109 * @param {number} length
1110 * @param {Uint8Array} nodeids
1111 */
1112 constructor(length, nodeids) {
1113 this.nodeids = nodeids;
1114 this.subtrees = [];
1115 for (let i = 0; i < length; ++i) {
1116 this.subtrees.push(null);
1117 }
1118 }
1119 /**
1120 * @param {number} i
1121 * @returns {Uint8Array}
1122 */
1123 getNodeID(i) {
1124 return new Uint8Array(
1125 this.nodeids.buffer,
1126 this.nodeids.byteOffset + (i * 6),
1127 6,
1128 );
1129 }
1130 // https://github.com/microsoft/TypeScript/issues/17227
1131 /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
1132 entries() {
1133 throw new Error();
1134 }
1135 /**
1136 * @param {number} _k
1137 * @returns {number}
1138 */
1139 getIndex(_k) {
1140 throw new Error();
1141 }
1142 /**
1143 * @param {number} _i
1144 * @returns {number}
1145 */
1146 getKey(_i) {
1147 throw new Error();
1148 }
1149 /**
1150 * @returns {Uint8Array}
1151 */
1152 getKeys() {
1153 throw new Error();
1154 }
1155 }
1156
1157 /**
1158 * A sorted array of search tree branches.
1159 *
1160 * @type {{
1161 * keys: Uint8Array,
1162 * nodeids: Uint8Array,
1163 * subtrees: Array<Promise<SearchTree>|null>,
1164 * }}
1165 */
1166 class SearchTreeBranchesArray extends SearchTreeBranches {
1167 /**
1168 * @param {Uint8Array} keys
1169 * @param {Uint8Array} nodeids
1170 */
1171 constructor(keys, nodeids) {
1172 super(keys.length, nodeids);
1173 this.keys = keys;
1174 let i = 1;
1175 while (i < this.keys.length) {
1176 if (this.keys[i - 1] >= this.keys[i]) {
1177 throw new Error("HERE");
1178 }
1179 i += 1;
1180 }
1181 }
1182 /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
1183 * entries() {
1184 let i = 0;
1185 const l = this.keys.length;
1186 while (i < l) {
1187 yield [this.keys[i], this.subtrees[i]];
1188 i += 1;
1189 }
1190 }
1191 /**
1192 * @param {number} k
1193 * @returns {number}
1194 */
1195 getIndex(k) {
1196 // Since length can't be bigger than 256,
1197 // left + right can't overflow.
1198 let left = 0;
1199 let right = this.keys.length - 1;
1200 while (left <= right) {
1201 const mid = (left + right) >> 1;
1202 if (this.keys[mid] < k) {
1203 left = mid + 1;
1204 } else if (this.keys[mid] > k) {
1205 right = mid - 1;
1206 } else {
1207 return mid;
1208 }
1209 }
1210 return -1;
1211 }
1212 /**
1213 * @param {number} i
1214 * @returns {number}
1215 */
1216 getKey(i) {
1217 return this.keys[i];
1218 }
1219 /**
1220 * @returns {Uint8Array}
1221 */
1222 getKeys() {
1223 return this.keys;
1224 }
1225 }
1226
1227 const EMPTY_SEARCH_TREE_BRANCHES = new SearchTreeBranchesArray(
1228 EMPTY_UINT8,
1229 EMPTY_UINT8,
1230 );
1231
1232 /** @type {number[]} */
1233 const SHORT_ALPHABITMAP_CHARS = [];
1234 for (let i = 0x61; i <= 0x7A; ++i) {
1235 if (i === 0x76 || i === 0x71) {
1236 // 24 entries, 26 letters, so we skip q and v
1237 continue;
1238 }
1239 SHORT_ALPHABITMAP_CHARS.push(i);
1240 }
1241
1242 /** @type {number[]} */
1243 const LONG_ALPHABITMAP_CHARS = [0x31, 0x32, 0x33, 0x34, 0x35, 0x36];
1244 for (let i = 0x61; i <= 0x7A; ++i) {
1245 LONG_ALPHABITMAP_CHARS.push(i);
1246 }
1247
1248 /**
1249 * @param {number[]} alphabitmap_chars
1250 * @param {number} width
1251 * @return {(typeof SearchTreeBranches)&{"ALPHABITMAP_CHARS": number[], "width": number}}
1252 */
1253 function makeSearchTreeBranchesAlphaBitmapClass(alphabitmap_chars, width) {
1254 const bitwidth = width * 8;
1255 const cls = class SearchTreeBranchesAlphaBitmap extends SearchTreeBranches {
1256 /**
1257 * @param {number} bitmap
1258 * @param {Uint8Array} nodeids
1259 */
1260 constructor(bitmap, nodeids) {
1261 super(nodeids.length / 6, nodeids);
1262 if (nodeids.length / 6 !== bitCount(bitmap)) {
1263 throw new Error(`mismatch ${bitmap} ${nodeids}`);
1264 }
1265 this.bitmap = bitmap;
1266 this.nodeids = nodeids;
1267 }
1268 /** @returns {Generator<[number, Promise<SearchTree>|null]>} */
1269 * entries() {
1270 let i = 0;
1271 let j = 0;
1272 while (i < bitwidth) {
1273 if (this.bitmap & (1 << i)) {
1274 yield [alphabitmap_chars[i], this.subtrees[j]];
1275 j += 1;
1276 }
1277 i += 1;
1278 }
1279 }
1280 /**
1281 * @param {number} k
1282 * @returns {number}
1283 */
1284 getIndex(k) {
1285 //return this.getKeys().indexOf(k);
1286 const ix = alphabitmap_chars.indexOf(k);
1287 if (ix < 0) {
1288 return ix;
1289 }
1290 const result = bitCount(~(0xffffffff << ix) & this.bitmap);
1291 return result >= this.subtrees.length ? -1 : result;
1292 }
1293 /**
1294 * @param {number} branch_index
1295 * @returns {number}
1296 */
1297 getKey(branch_index) {
1298 //return this.getKeys()[branch_index];
1299 let alpha_index = 0;
1300 while (branch_index >= 0) {
1301 if (this.bitmap & (1 << alpha_index)) {
1302 branch_index -= 1;
1303 }
1304 alpha_index += 1;
1305 }
1306 return alphabitmap_chars[alpha_index];
1307 }
1308 /**
1309 * @returns {Uint8Array}
1310 */
1311 getKeys() {
1312 const length = bitCount(this.bitmap);
1313 const result = new Uint8Array(length);
1314 let result_index = 0;
1315 for (let alpha_index = 0; alpha_index < bitwidth; ++alpha_index) {
1316 if (this.bitmap & (1 << alpha_index)) {
1317 result[result_index] = alphabitmap_chars[alpha_index];
1318 result_index += 1;
1319 }
1320 }
1321 return result;
1322 }
1323 };
1324 cls.ALPHABITMAP_CHARS = alphabitmap_chars;
1325 cls.width = width;
1326 return cls;
1327 }
1328
1329 const SearchTreeBranchesShortAlphaBitmap =
1330 makeSearchTreeBranchesAlphaBitmapClass(SHORT_ALPHABITMAP_CHARS, 3);
1331
1332 const SearchTreeBranchesLongAlphaBitmap =
1333 makeSearchTreeBranchesAlphaBitmapClass(LONG_ALPHABITMAP_CHARS, 4);
1334
1335 /**
1336 * A [suffix tree], used for name-based search.
1337 *
1338 * This data structure is used to drive substring matches,
1339 * such as matching the query "link" to `LinkedList`,
1340 * and Lev-distance matches, such as matching the
1341 * query "hahsmap" to `HashMap`.
1342 *
1343 * [suffix tree]: https://en.wikipedia.org/wiki/Suffix_tree
1344 *
1345 * branches
1346 * : A sorted-array map of subtrees.
1347 *
1348 * data
1349 * : The substring represented by this node. The root node
1350 * is always empty.
1351 *
1352 * leaves_suffix
1353 * : The IDs of every entry that matches. Levenshtein matches
1354 * won't include these.
1355 *
1356 * leaves_whole
1357 * : The IDs of every entry that matches exactly. Levenshtein matches
1358 * will include these.
1359 *
1360 * @type {{
1361 * might_have_prefix_branches: SearchTreeBranches,
1362 * branches: SearchTreeBranches,
1363 * data: Uint8Array,
1364 * leaves_suffix: RoaringBitmap,
1365 * leaves_whole: RoaringBitmap,
1366 * }}
1367 */
1368 class SearchTree {
1369 /**
1370 * @param {SearchTreeBranches} branches
1371 * @param {SearchTreeBranches} might_have_prefix_branches
1372 * @param {Uint8Array} data
1373 * @param {RoaringBitmap} leaves_whole
1374 * @param {RoaringBitmap} leaves_suffix
1375 */
1376 constructor(
1377 branches,
1378 might_have_prefix_branches,
1379 data,
1380 leaves_whole,
1381 leaves_suffix,
1382 ) {
1383 this.might_have_prefix_branches = might_have_prefix_branches;
1384 this.branches = branches;
1385 this.data = data;
1386 this.leaves_suffix = leaves_suffix;
1387 this.leaves_whole = leaves_whole;
1388 }
1389 /**
1390 * Returns the Trie for the root node.
1391 *
1392 * A Trie pointer refers to a single node in a logical decompressed search tree
1393 * (the real search tree is compressed).
1394 *
1395 * @return {Trie}
1396 */
1397 trie() {
1398 return new Trie(this, 0);
1399 }
1400
1401 /**
1402 * Return the trie representing `name`
1403 * @param {Uint8Array|string} name
1404 * @returns {Promise<Trie?>}
1405 */
1406 async search(name) {
1407 if (typeof name === "string") {
1408 const utf8encoder = new TextEncoder();
1409 name = utf8encoder.encode(name);
1410 }
1411 let trie = this.trie();
1412 for (const datum of name) {
1413 // code point definitely exists
1414 const newTrie = trie.child(datum);
1415 if (newTrie) {
1416 trie = await newTrie;
1417 } else {
1418 return null;
1419 }
1420 }
1421 return trie;
1422 }
1423
1424 /**
1425 * @param {Uint8Array|string} name
1426 * @returns {AsyncGenerator<Trie>}
1427 */
1428 async* searchLev(name) {
1429 if (typeof name === "string") {
1430 const utf8encoder = new TextEncoder();
1431 name = utf8encoder.encode(name);
1432 }
1433 const w = name.length;
1434 if (w < 3) {
1435 const trie = await this.search(name);
1436 if (trie !== null) {
1437 yield trie;
1438 }
1439 return;
1440 }
1441 const levParams = w >= 6 ?
1442 new Lev2TParametricDescription(w) :
1443 new Lev1TParametricDescription(w);
1444 /** @type {Array<[Promise<Trie>, number]>} */
1445 const stack = [[Promise.resolve(this.trie()), 0]];
1446 const n = levParams.n;
1447 while (stack.length !== 0) {
1448 // It's not empty
1449 /** @type {[Promise<Trie>, number]} */
1450 //@ts-expect-error
1451 const [triePromise, levState] = stack.pop();
1452 const trie = await triePromise;
1453 for (const byte of trie.keysExcludeSuffixOnly()) {
1454 const levPos = levParams.getPosition(levState);
1455 const vector = levParams.getVector(
1456 name,
1457 byte,
1458 levPos,
1459 Math.min(w, levPos + (2 * n) + 1),
1460 );
1461 const newLevState = levParams.transition(
1462 levState,
1463 levPos,
1464 vector,
1465 );
1466 if (newLevState >= 0) {
1467 const child = trie.child(byte);
1468 if (child) {
1469 stack.push([child, newLevState]);
1470 if (levParams.isAccept(newLevState)) {
1471 yield child;
1472 }
1473 }
1474 }
1475 }
1476 }
1477 }
1478 }
1479
1480 /**
1481 * A representation of a set of strings in the search index,
1482 * as a subset of the entire tree.
1483 */
1484 class Trie {
1485 /**
1486 * @param {SearchTree} tree
1487 * @param {number} offset
1488 */
1489 constructor(tree, offset) {
1490 this.tree = tree;
1491 this.offset = offset;
1492 }
1493
1494 /**
1495 * All exact matches for the string represented by this node.
1496 * @returns {RoaringBitmap}
1497 */
1498 matches() {
1499 if (this.offset === this.tree.data.length) {
1500 return this.tree.leaves_whole;
1501 } else {
1502 return EMPTY_BITMAP;
1503 }
1504 }
1505
1506 /**
1507 * All matches for strings that contain the string represented by this node.
1508 * @returns {AsyncGenerator<RoaringBitmap>}
1509 */
1510 async* substringMatches() {
1511 /** @type {Promise<SearchTree>[]} */
1512 let layer = [Promise.resolve(this.tree)];
1513 while (layer.length) {
1514 const current_layer = layer;
1515 layer = [];
1516 for await (const tree of current_layer) {
1517 yield tree.leaves_whole.union(tree.leaves_suffix);
1518 }
1519 /** @type {HashTable<[number, SearchTree][]>} */
1520 const subnodes = new HashTable();
1521 for await (const node of current_layer) {
1522 const branches = node.branches;
1523 const l = branches.subtrees.length;
1524 for (let i = 0; i < l; ++i) {
1525 const subtree = branches.subtrees[i];
1526 if (subtree) {
1527 layer.push(subtree);
1528 } else if (subtree === null) {
1529 const byte = branches.getKey(i);
1530 const newnode = branches.getNodeID(i);
1531 if (!newnode) {
1532 throw new Error(`malformed tree; no node for key ${byte}`);
1533 } else {
1534 let subnode_list = subnodes.get(newnode);
1535 if (!subnode_list) {
1536 subnode_list = [[byte, node]];
1537 subnodes.set(newnode, subnode_list);
1538 } else {
1539 subnode_list.push([byte, node]);
1540 }
1541 }
1542 } else {
1543 throw new Error(`malformed tree; index ${i} does not exist`);
1544 }
1545 }
1546 }
1547 for (const [newnode, subnode_list] of subnodes.entries()) {
1548 const res = registry.searchTreeLoadByNodeID(newnode);
1549 for (const [byte, node] of subnode_list) {
1550 const branches = node.branches;
1551 const might_have_prefix_branches = node.might_have_prefix_branches;
1552 const i = branches.getIndex(byte);
1553 branches.subtrees[i] = res;
1554 const mhpI = might_have_prefix_branches.getIndex(byte);
1555 if (mhpI !== -1) {
1556 might_have_prefix_branches.subtrees[mhpI] = res;
1557 }
1558 }
1559 layer.push(res);
1560 }
1561 }
1562 }
1563
1564 /**
1565 * All matches for strings that start with the string represented by this node.
1566 * @returns {AsyncGenerator<RoaringBitmap>}
1567 */
1568 async* prefixMatches() {
1569 /** @type {{node: Promise<SearchTree>, len: number}[]} */
1570 let layer = [{node: Promise.resolve(this.tree), len: 0}];
1571 // https://en.wikipedia.org/wiki/Heap_(data_structure)#Implementation_using_arrays
1572 /** @type {{bitmap: RoaringBitmap, length: number}[]} */
1573 const backlog = [];
1574 while (layer.length !== 0 || backlog.length !== 0) {
1575 const current_layer = layer;
1576 layer = [];
1577 let minLength = null;
1578 // push every entry in the current layer into the backlog,
1579 // a min-heap of result entries
1580 // we then yield the smallest ones (can't yield bigger ones
1581 // if we want to do them in order)
1582 for (const {node, len} of current_layer) {
1583 const tree = await node;
1584 const length = len + tree.data.length;
1585 if (minLength === null || length < minLength) {
1586 minLength = length;
1587 }
1588 let backlogSlot = backlog.length;
1589 backlog.push({bitmap: tree.leaves_whole, length});
1590 while (backlogSlot > 0 &&
1591 backlog[backlogSlot].length < backlog[(backlogSlot - 1) >> 1].length
1592 ) {
1593 const parentSlot = (backlogSlot - 1) >> 1;
1594 const parent = backlog[parentSlot];
1595 backlog[parentSlot] = backlog[backlogSlot];
1596 backlog[backlogSlot] = parent;
1597 backlogSlot = parentSlot;
1598 }
1599 }
1600 // yield nodes in length order, smallest one first
1601 // we know that, whatever the smallest item is
1602 // every child will be bigger than that
1603 while (backlog.length !== 0) {
1604 const backlogEntry = backlog[0];
1605 if (minLength !== null && backlogEntry.length > minLength) {
1606 break;
1607 }
1608 if (!backlogEntry.bitmap.isEmpty()) {
1609 yield backlogEntry.bitmap;
1610 }
1611 backlog[0] = backlog[backlog.length - 1];
1612 backlog.length -= 1;
1613 let backlogSlot = 0;
1614 const backlogLength = backlog.length;
1615 while (backlogSlot < backlogLength) {
1616 const leftSlot = (backlogSlot << 1) + 1;
1617 const rightSlot = (backlogSlot << 1) + 2;
1618 let smallest = backlogSlot;
1619 if (leftSlot < backlogLength &&
1620 backlog[leftSlot].length < backlog[smallest].length
1621 ) {
1622 smallest = leftSlot;
1623 }
1624 if (rightSlot < backlogLength &&
1625 backlog[rightSlot].length < backlog[smallest].length
1626 ) {
1627 smallest = rightSlot;
1628 }
1629 if (smallest === backlogSlot) {
1630 break;
1631 } else {
1632 const tmp = backlog[backlogSlot];
1633 backlog[backlogSlot] = backlog[smallest];
1634 backlog[smallest] = tmp;
1635 backlogSlot = smallest;
1636 }
1637 }
1638 }
1639 // if we still have more subtrees to walk, then keep going
1640 /** @type {HashTable<{byte: number, tree: SearchTree, len: number}[]>} */
1641 const subnodes = new HashTable();
1642 for await (const {node, len} of current_layer) {
1643 const tree = await node;
1644 const length = len + tree.data.length;
1645 const mhp_branches = tree.might_have_prefix_branches;
1646 const l = mhp_branches.subtrees.length;
1647 for (let i = 0; i < l; ++i) {
1648 const len = length + 1;
1649 const subtree = mhp_branches.subtrees[i];
1650 if (subtree) {
1651 layer.push({node: subtree, len});
1652 } else if (subtree === null) {
1653 const byte = mhp_branches.getKey(i);
1654 const newnode = mhp_branches.getNodeID(i);
1655 if (!newnode) {
1656 throw new Error(`malformed tree; no node for key ${byte}`);
1657 } else {
1658 let subnode_list = subnodes.get(newnode);
1659 if (!subnode_list) {
1660 subnode_list = [{byte, tree, len}];
1661 subnodes.set(newnode, subnode_list);
1662 } else {
1663 subnode_list.push({byte, tree, len});
1664 }
1665 }
1666 } else {
1667 throw new Error(`malformed tree; index ${i} does not exist`);
1668 }
1669 }
1670 }
1671 for (const [newnode, subnode_list] of subnodes.entries()) {
1672 const res = registry.searchTreeLoadByNodeID(newnode);
1673 let len = Number.MAX_SAFE_INTEGER;
1674 for (const {byte, tree, len: subtreelen} of subnode_list) {
1675 if (subtreelen < len) {
1676 len = subtreelen;
1677 }
1678 const mhp_branches = tree.might_have_prefix_branches;
1679 const i = mhp_branches.getIndex(byte);
1680 mhp_branches.subtrees[i] = res;
1681 const branches = tree.branches;
1682 const bi = branches.getIndex(byte);
1683 branches.subtrees[bi] = res;
1684 }
1685 layer.push({node: res, len});
1686 }
1687 }
1688 }
1689
1690 /**
1691 * Returns all keys that are children of this node.
1692 * @returns {Uint8Array}
1693 */
1694 keys() {
1695 const data = this.tree.data;
1696 if (this.offset === data.length) {
1697 return this.tree.branches.getKeys();
1698 } else {
1699 return Uint8Array.of(data[this.offset]);
1700 }
1701 }
1702
1703 /**
1704 * Returns all nodes that are direct children of this node.
1705 * @returns {[number, Promise<Trie>][]}
1706 */
1707 children() {
1708 const data = this.tree.data;
1709 if (this.offset === data.length) {
1710 /** @type {[number, Promise<Trie>][]} */
1711 const nodes = [];
1712 let i = 0;
1713 for (const [k, v] of this.tree.branches.entries()) {
1714 /** @type {Promise<SearchTree>} */
1715 let node;
1716 if (v) {
1717 node = v;
1718 } else {
1719 const newnode = this.tree.branches.getNodeID(i);
1720 if (!newnode) {
1721 throw new Error(`malformed tree; no hash for key ${k}: ${newnode} \
1722 ${this.tree.branches.nodeids} ${this.tree.branches.getKeys()}`);
1723 }
1724 node = registry.searchTreeLoadByNodeID(newnode);
1725 this.tree.branches.subtrees[i] = node;
1726 const mhpI = this.tree.might_have_prefix_branches.getIndex(k);
1727 if (mhpI !== -1) {
1728 this.tree.might_have_prefix_branches.subtrees[mhpI] = node;
1729 }
1730 }
1731 nodes.push([k, node.then(node => node.trie())]);
1732 i += 1;
1733 }
1734 return nodes;
1735 } else {
1736 /** @type {number} */
1737 const codePoint = data[this.offset];
1738 const trie = new Trie(this.tree, this.offset + 1);
1739 return [[codePoint, Promise.resolve(trie)]];
1740 }
1741 }
1742
1743 /**
1744 * Returns all keys that are children of this node.
1745 * @returns {Uint8Array}
1746 */
1747 keysExcludeSuffixOnly() {
1748 const data = this.tree.data;
1749 if (this.offset === data.length) {
1750 return this.tree.might_have_prefix_branches.getKeys();
1751 } else {
1752 return Uint8Array.of(data[this.offset]);
1753 }
1754 }
1755
1756 /**
1757 * Returns all nodes that are direct children of this node.
1758 * @returns {[number, Promise<Trie>][]}
1759 */
1760 childrenExcludeSuffixOnly() {
1761 const data = this.tree.data;
1762 if (this.offset === data.length) {
1763 /** @type {[number, Promise<Trie>][]} */
1764 const nodes = [];
1765 let i = 0;
1766 for (const [k, v] of this.tree.might_have_prefix_branches.entries()) {
1767 /** @type {Promise<SearchTree>} */
1768 let node;
1769 if (v) {
1770 node = v;
1771 } else {
1772 const newnode = this.tree.might_have_prefix_branches.getNodeID(i);
1773 if (!newnode) {
1774 throw new Error(`malformed tree; no node for key ${k}`);
1775 }
1776 node = registry.searchTreeLoadByNodeID(newnode);
1777 this.tree.might_have_prefix_branches.subtrees[i] = node;
1778 this.tree.branches.subtrees[this.tree.branches.getIndex(k)] = node;
1779 }
1780 nodes.push([k, node.then(node => node.trie())]);
1781 i += 1;
1782 }
1783 return nodes;
1784 } else {
1785 /** @type {number} */
1786 const codePoint = data[this.offset];
1787 const trie = new Trie(this.tree, this.offset + 1);
1788 return [[codePoint, Promise.resolve(trie)]];
1789 }
1790 }
1791
1792 /**
1793 * Returns a single node that is a direct child of this node.
1794 * @param {number} byte
1795 * @returns {Promise<Trie>?}
1796 */
1797 child(byte) {
1798 if (this.offset === this.tree.data.length) {
1799 const i = this.tree.branches.getIndex(byte);
1800 if (i !== -1) {
1801 let branch = this.tree.branches.subtrees[i];
1802 if (branch === null) {
1803 const newnode = this.tree.branches.getNodeID(i);
1804 if (!newnode) {
1805 throw new Error(`malformed tree; no node for key ${byte}`);
1806 }
1807 branch = registry.searchTreeLoadByNodeID(newnode);
1808 this.tree.branches.subtrees[i] = branch;
1809 const mhpI = this.tree.might_have_prefix_branches.getIndex(byte);
1810 if (mhpI !== -1) {
1811 this.tree.might_have_prefix_branches.subtrees[mhpI] = branch;
1812 }
1813 }
1814 return branch.then(branch => branch.trie());
1815 }
1816 } else if (this.tree.data[this.offset] === byte) {
1817 return Promise.resolve(new Trie(this.tree, this.offset + 1));
1818 }
1819 return null;
1820 }
1821 }
1822
1823 class DataColumn {
1824 /**
1825 * Construct the wrapper object for a data column.
1826 * @param {number[]} counts
1827 * @param {Uint8Array} hashes
1828 * @param {RoaringBitmap} emptyset
1829 * @param {string} name
1830 */
1831 constructor(counts, hashes, emptyset, name) {
1832 this.hashes = hashes;
1833 this.emptyset = emptyset;
1834 this.name = name;
1835 /** @type {{"hash": Uint8Array, "data": Promise<Uint8Array[]>?, "end": number}[]} */
1836 this.buckets = [];
1837 this.bucket_keys = [];
1838 const l = counts.length;
1839 let k = 0;
1840 let totalLength = 0;
1841 for (let i = 0; i < l; ++i) {
1842 const count = counts[i];
1843 totalLength += count;
1844 const start = k;
1845 for (let j = 0; j < count; ++j) {
1846 if (emptyset.contains(k)) {
1847 j -= 1;
1848 }
1849 k += 1;
1850 }
1851 const end = k;
1852 const bucket = {hash: hashes.subarray(i * 6, (i + 1) * 6), data: null, end, count};
1853 this.buckets.push(bucket);
1854 this.bucket_keys.push(start);
1855 }
1856 this.length = totalLength;
1857 }
1858 /**
1859 * Check if a cell contains the empty string.
1860 * @param {number} id
1861 * @returns {boolean}
1862 */
1863 isEmpty(id) {
1864 return this.emptyset.contains(id);
1865 }
1866 /**
1867 * Look up a cell by row ID.
1868 * @param {number} id
1869 * @returns {Promise<Uint8Array|undefined>}
1870 */
1871 async at(id) {
1872 if (this.emptyset.contains(id)) {
1873 return Promise.resolve(EMPTY_UINT8);
1874 } else {
1875 let idx = -1;
1876 while (this.bucket_keys[idx + 1] <= id) {
1877 idx += 1;
1878 }
1879 if (idx === -1 || idx >= this.bucket_keys.length) {
1880 return Promise.resolve(undefined);
1881 } else {
1882 const start = this.bucket_keys[idx];
1883 const {hash, end} = this.buckets[idx];
1884 let data = this.buckets[idx].data;
1885 if (data === null) {
1886 const dataSansEmptyset = await registry.dataLoadByNameAndHash(
1887 this.name,
1888 hash,
1889 );
1890 // After the `await` resolves, another task might fill
1891 // in the data. If so, we should use that.
1892 data = this.buckets[idx].data;
1893 if (data !== null) {
1894 return (await data)[id - start];
1895 }
1896 /** @type {(Uint8Array[])|null} */
1897 let dataWithEmptyset = null;
1898 let pos = start;
1899 let insertCount = 0;
1900 while (pos < end) {
1901 if (this.emptyset.contains(pos)) {
1902 if (dataWithEmptyset === null) {
1903 dataWithEmptyset = dataSansEmptyset.splice(0, insertCount);
1904 } else if (insertCount !== 0) {
1905 dataWithEmptyset.push(
1906 ...dataSansEmptyset.splice(0, insertCount),
1907 );
1908 }
1909 insertCount = 0;
1910 dataWithEmptyset.push(EMPTY_UINT8);
1911 } else {
1912 insertCount += 1;
1913 }
1914 pos += 1;
1915 }
1916 data = Promise.resolve(
1917 dataWithEmptyset === null ?
1918 dataSansEmptyset :
1919 dataWithEmptyset.concat(dataSansEmptyset),
1920 );
1921 this.buckets[idx].data = data;
1922 }
1923 return (await data)[id - start];
1924 }
1925 }
1926 }
1927 }
1928
1929 class Database {
1930 /**
1931 * The primary frontend for accessing data in this index.
1932 *
1933 * @param {Map<string, SearchTree>} searchTreeRoots
1934 * @param {Map<string, DataColumn>} dataColumns
1935 */
1936 constructor(searchTreeRoots, dataColumns) {
1937 this.searchTreeRoots = searchTreeRoots;
1938 this.dataColumns = dataColumns;
1939 }
1940 /**
1941 * Search a column by name, returning verbatim matched IDs.
1942 * @param {string} colname
1943 * @returns {SearchTree|undefined}
1944 */
1945 getIndex(colname) {
1946 return this.searchTreeRoots.get(colname);
1947 }
1948 /**
1949 * Look up a cell by column ID and row ID.
1950 * @param {string} colname
1951 * @returns {DataColumn|undefined}
1952 */
1953 getData(colname) {
1954 return this.dataColumns.get(colname);
1955 }
1956 }
1957
1958 /**
1959 * Load a data column.
1960 * @param {Uint8Array} data
1961 */
1962 function loadColumnFromBytes(data) {
1963 const hashBuf = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0);
1964 const truncatedHash = hashBuf.subarray(2, 8);
1965 siphashOfBytes(data, 0, 0, 0, 0, hashBuf);
1966 const cb = registry.dataColumnLoadPromiseCallbacks.get(truncatedHash);
1967 if (cb) {
1968 const backrefs = [];
1969 const dataSansEmptyset = [];
1970 let i = 0;
1971 const l = data.length;
1972 while (i < l) {
1973 let c = data[i];
1974 if (c >= 48 && c <= 63) { // 48 = "0", 63 = "?"
1975 dataSansEmptyset.push(backrefs[c - 48]);
1976 i += 1;
1977 } else {
1978 let n = 0;
1979 while (c < 96) { // 96 = "`"
1980 n = (n << 4) | (c & 0xF);
1981 i += 1;
1982 c = data[i];
1983 }
1984 n = (n << 4) | (c & 0xF);
1985 i += 1;
1986 const item = data.subarray(i, i + n);
1987 dataSansEmptyset.push(item);
1988 i += n;
1989 backrefs.unshift(item);
1990 if (backrefs.length > 16) {
1991 backrefs.pop();
1992 }
1993 }
1994 }
1995 cb(null, dataSansEmptyset);
1996 }
1997 }
1998
1999 /**
2000 * @param {string} inputBase64
2001 * @returns {[Uint8Array, SearchTree]}
2002 */
2003 function makeSearchTreeFromBase64(inputBase64) {
2004 const input = makeUint8ArrayFromBase64(inputBase64);
2005 let i = 0;
2006 const l = input.length;
2007 /** @type {HashTable<SearchTree>} */
2008 const stash = new HashTable();
2009 const hash = Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0);
2010 const truncatedHash = new Uint8Array(hash.buffer, 2, 6);
2011 // used for handling compressed (that is, relative-offset) nodes
2012 /** @type {{hash: Uint8Array, used: boolean}[]} */
2013 const hash_history = [];
2014 /** @type {Uint8Array[]} */
2015 const data_history = [];
2016 let canonical = EMPTY_UINT8;
2017 /** @type {SearchTree} */
2018 let tree = new SearchTree(
2019 EMPTY_SEARCH_TREE_BRANCHES,
2020 EMPTY_SEARCH_TREE_BRANCHES,
2021 EMPTY_UINT8,
2022 EMPTY_BITMAP,
2023 EMPTY_BITMAP,
2024 );
2025 /**
2026 * @param {Uint8Array} input
2027 * @param {number} i
2028 * @param {number} compression_tag
2029 * @returns {{
2030 * "cpbranches": Uint8Array,
2031 * "csbranches": Uint8Array,
2032 * "might_have_prefix_branches": SearchTreeBranches,
2033 * "branches": SearchTreeBranches,
2034 * "cpnodes": Uint8Array,
2035 * "csnodes": Uint8Array,
2036 * "consumed_len_bytes": number,
2037 * }}
2038 */
2039 function makeBranchesFromBinaryData(
2040 input,
2041 i,
2042 compression_tag,
2043 ) {
2044 const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0x00;
2045 const is_stack_compressed = (compression_tag & 0x02) !== 0;
2046 const is_long_compressed = (compression_tag & 0x04) !== 0;
2047 const all_children_are_compressed =
2048 (compression_tag & 0xF0) === 0xF0 && !is_long_compressed;
2049 const any_children_are_compressed =
2050 (compression_tag & 0xF0) !== 0x00 || is_long_compressed;
2051 const start_point = i;
2052 let cplen;
2053 let cslen;
2054 let alphabitmap = null;
2055 if (is_pure_suffixes_only_node) {
2056 cplen = 0;
2057 cslen = input[i];
2058 i += 1;
2059 if (cslen >= 0xc0) {
2060 alphabitmap = SearchTreeBranchesLongAlphaBitmap;
2061 cslen = cslen & 0x3F;
2062 } else if (cslen >= 0x80) {
2063 alphabitmap = SearchTreeBranchesShortAlphaBitmap;
2064 cslen = cslen & 0x7F;
2065 }
2066 } else {
2067 cplen = input[i];
2068 i += 1;
2069 cslen = input[i];
2070 i += 1;
2071 if (cplen === 0xff && cslen === 0xff) {
2072 cplen = 0x100;
2073 cslen = 0;
2074 } else if (cplen >= 0xc0 && cslen >= 0xc0) {
2075 alphabitmap = SearchTreeBranchesLongAlphaBitmap;
2076 cplen = cplen & 0x3F;
2077 cslen = cslen & 0x3F;
2078 } else if (cplen >= 0x80 && cslen >= 0x80) {
2079 alphabitmap = SearchTreeBranchesShortAlphaBitmap;
2080 cplen = cplen & 0x7F;
2081 cslen = cslen & 0x7F;
2082 }
2083 }
2084 let j = 0;
2085 /** @type {Uint8Array} */
2086 let cpnodes;
2087 if (any_children_are_compressed) {
2088 cpnodes = cplen === 0 ? EMPTY_UINT8 : new Uint8Array(cplen * 6);
2089 while (j < cplen) {
2090 const is_compressed = all_children_are_compressed ||
2091 ((0x10 << j) & compression_tag) !== 0;
2092 if (is_compressed) {
2093 let slot = hash_history.length - 1;
2094 if (is_stack_compressed) {
2095 while (hash_history[slot].used) {
2096 slot -= 1;
2097 }
2098 } else {
2099 slot -= input[i];
2100 i += 1;
2101 }
2102 hash_history[slot].used = true;
2103 cpnodes.set(
2104 hash_history[slot].hash,
2105 j * 6,
2106 );
2107 } else {
2108 const joff = j * 6;
2109 cpnodes[joff + 0] = input[i + 0];
2110 cpnodes[joff + 1] = input[i + 1];
2111 cpnodes[joff + 2] = input[i + 2];
2112 cpnodes[joff + 3] = input[i + 3];
2113 cpnodes[joff + 4] = input[i + 4];
2114 cpnodes[joff + 5] = input[i + 5];
2115 i += 6;
2116 }
2117 j += 1;
2118 }
2119 } else {
2120 cpnodes = cplen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cplen * 6));
2121 i += cplen * 6;
2122 }
2123 j = 0;
2124 /** @type {Uint8Array} */
2125 let csnodes;
2126 if (any_children_are_compressed) {
2127 csnodes = cslen === 0 ? EMPTY_UINT8 : new Uint8Array(cslen * 6);
2128 while (j < cslen) {
2129 const is_compressed = all_children_are_compressed ||
2130 ((0x10 << (cplen + j)) & compression_tag) !== 0;
2131 if (is_compressed) {
2132 let slot = hash_history.length - 1;
2133 if (is_stack_compressed) {
2134 while (hash_history[slot].used) {
2135 slot -= 1;
2136 }
2137 } else {
2138 slot -= input[i];
2139 i += 1;
2140 }
2141 hash_history[slot].used = true;
2142 csnodes.set(
2143 hash_history[slot].hash,
2144 j * 6,
2145 );
2146 } else {
2147 const joff = j * 6;
2148 csnodes[joff + 0] = input[i + 0];
2149 csnodes[joff + 1] = input[i + 1];
2150 csnodes[joff + 2] = input[i + 2];
2151 csnodes[joff + 3] = input[i + 3];
2152 csnodes[joff + 4] = input[i + 4];
2153 csnodes[joff + 5] = input[i + 5];
2154 i += 6;
2155 }
2156 j += 1;
2157 }
2158 } else {
2159 csnodes = cslen === 0 ? EMPTY_UINT8 : input.subarray(i, i + (cslen * 6));
2160 i += cslen * 6;
2161 }
2162 let cpbranches;
2163 let might_have_prefix_branches;
2164 if (cplen === 0) {
2165 cpbranches = EMPTY_UINT8;
2166 might_have_prefix_branches = EMPTY_SEARCH_TREE_BRANCHES;
2167 } else if (alphabitmap) {
2168 cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width);
2169 const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) |
2170 (input[i + 2] << 16) |
2171 (input[i + 1] << 8) |
2172 input[i];
2173 might_have_prefix_branches = new alphabitmap(branchset, cpnodes);
2174 i += alphabitmap.width;
2175 } else {
2176 cpbranches = new Uint8Array(input.buffer, i + input.byteOffset, cplen);
2177 might_have_prefix_branches = new SearchTreeBranchesArray(cpbranches, cpnodes);
2178 i += cplen;
2179 }
2180 let csbranches;
2181 let branches;
2182 if (cslen === 0) {
2183 csbranches = EMPTY_UINT8;
2184 branches = might_have_prefix_branches;
2185 } else if (alphabitmap) {
2186 csbranches = new Uint8Array(input.buffer, i + input.byteOffset, alphabitmap.width);
2187 const branchset = (alphabitmap.width === 4 ? (input[i + 3] << 24) : 0) |
2188 (input[i + 2] << 16) |
2189 (input[i + 1] << 8) |
2190 input[i];
2191 if (cplen === 0) {
2192 branches = new alphabitmap(branchset, csnodes);
2193 } else {
2194 const cpoffset = i - alphabitmap.width;
2195 const cpbranchset =
2196 (alphabitmap.width === 4 ? (input[cpoffset + 3] << 24) : 0) |
2197 (input[cpoffset + 2] << 16) |
2198 (input[cpoffset + 1] << 8) |
2199 input[cpoffset];
2200 const hashes = new Uint8Array((cplen + cslen) * 6);
2201 let cpi = 0;
2202 let csi = 0;
2203 let j = 0;
2204 for (let k = 0; k < alphabitmap.ALPHABITMAP_CHARS.length; k += 1) {
2205 if (branchset & (1 << k)) {
2206 hashes[j + 0] = csnodes[csi + 0];
2207 hashes[j + 1] = csnodes[csi + 1];
2208 hashes[j + 2] = csnodes[csi + 2];
2209 hashes[j + 3] = csnodes[csi + 3];
2210 hashes[j + 4] = csnodes[csi + 4];
2211 hashes[j + 5] = csnodes[csi + 5];
2212 j += 6;
2213 csi += 6;
2214 } else if (cpbranchset & (1 << k)) {
2215 hashes[j + 0] = cpnodes[cpi + 0];
2216 hashes[j + 1] = cpnodes[cpi + 1];
2217 hashes[j + 2] = cpnodes[cpi + 2];
2218 hashes[j + 3] = cpnodes[cpi + 3];
2219 hashes[j + 4] = cpnodes[cpi + 4];
2220 hashes[j + 5] = cpnodes[cpi + 5];
2221 j += 6;
2222 cpi += 6;
2223 }
2224 }
2225 branches = new alphabitmap(branchset | cpbranchset, hashes);
2226 }
2227 i += alphabitmap.width;
2228 } else {
2229 csbranches = new Uint8Array(input.buffer, i + input.byteOffset, cslen);
2230 if (cplen === 0) {
2231 branches = new SearchTreeBranchesArray(csbranches, csnodes);
2232 } else {
2233 const branchset = new Uint8Array(cplen + cslen);
2234 const hashes = new Uint8Array((cplen + cslen) * 6);
2235 let cpi = 0;
2236 let csi = 0;
2237 let j = 0;
2238 while (cpi < cplen || csi < cslen) {
2239 if (cpi >= cplen || (csi < cslen && cpbranches[cpi] > csbranches[csi])) {
2240 branchset[j] = csbranches[csi];
2241 const joff = j * 6;
2242 const csioff = csi * 6;
2243 hashes[joff + 0] = csnodes[csioff + 0];
2244 hashes[joff + 1] = csnodes[csioff + 1];
2245 hashes[joff + 2] = csnodes[csioff + 2];
2246 hashes[joff + 3] = csnodes[csioff + 3];
2247 hashes[joff + 4] = csnodes[csioff + 4];
2248 hashes[joff + 5] = csnodes[csioff + 5];
2249 csi += 1;
2250 } else {
2251 branchset[j] = cpbranches[cpi];
2252 const joff = j * 6;
2253 const cpioff = cpi * 6;
2254 hashes[joff + 0] = cpnodes[cpioff + 0];
2255 hashes[joff + 1] = cpnodes[cpioff + 1];
2256 hashes[joff + 2] = cpnodes[cpioff + 2];
2257 hashes[joff + 3] = cpnodes[cpioff + 3];
2258 hashes[joff + 4] = cpnodes[cpioff + 4];
2259 hashes[joff + 5] = cpnodes[cpioff + 5];
2260 cpi += 1;
2261 }
2262 j += 1;
2263 }
2264 branches = new SearchTreeBranchesArray(branchset, hashes);
2265 }
2266 i += cslen;
2267 }
2268 return {
2269 consumed_len_bytes: i - start_point,
2270 cpbranches,
2271 csbranches,
2272 cpnodes,
2273 csnodes,
2274 branches,
2275 might_have_prefix_branches,
2276 };
2277 }
2278 while (i < l) {
2279 const start = i;
2280 let data;
2281 // compression_tag = 1 means pure-suffixes-only,
2282 // which is not considered "compressed" for the purposes of this loop
2283 // because that's the canonical, hashed version of the data
2284 let compression_tag = input[i];
2285 const is_pure_suffixes_only_node = (compression_tag & 0x01) !== 0;
2286 if (compression_tag > 1) {
2287 // compressed node
2288 const is_long_compressed = (compression_tag & 0x04) !== 0;
2289 const is_data_compressed = (compression_tag & 0x08) !== 0;
2290 i += 1;
2291 if (is_long_compressed) {
2292 compression_tag |= input[i] << 8;
2293 i += 1;
2294 compression_tag |= input[i] << 16;
2295 i += 1;
2296 }
2297 let dlen = input[i];
2298 i += 1;
2299 if (is_data_compressed) {
2300 data = data_history[data_history.length - dlen - 1];
2301 dlen = data.length;
2302 } else {
2303 data = dlen === 0 ?
2304 EMPTY_UINT8 :
2305 new Uint8Array(input.buffer, i + input.byteOffset, dlen);
2306 i += dlen;
2307 }
2308 const coffset = i;
2309 const {
2310 cpbranches,
2311 csbranches,
2312 cpnodes,
2313 csnodes,
2314 consumed_len_bytes: branches_consumed_len_bytes,
2315 branches,
2316 might_have_prefix_branches,
2317 } = makeBranchesFromBinaryData(input, i, compression_tag);
2318 i += branches_consumed_len_bytes;
2319 let whole;
2320 let suffix;
2321 if (is_pure_suffixes_only_node) {
2322 whole = EMPTY_BITMAP;
2323 suffix = input[i] === 0 ?
2324 EMPTY_BITMAP1 :
2325 new RoaringBitmap(input, i);
2326 i += suffix.consumed_len_bytes;
2327 } else if (input[i] === 0xff) {
2328 whole = EMPTY_BITMAP;
2329 suffix = EMPTY_BITMAP1;
2330 i += 1;
2331 } else {
2332 whole = input[i] === 0 ?
2333 EMPTY_BITMAP1 :
2334 new RoaringBitmap(input, i);
2335 i += whole.consumed_len_bytes;
2336 suffix = input[i] === 0 ?
2337 EMPTY_BITMAP1 :
2338 new RoaringBitmap(input, i);
2339 i += suffix.consumed_len_bytes;
2340 }
2341 tree = new SearchTree(
2342 branches,
2343 might_have_prefix_branches,
2344 data,
2345 whole,
2346 suffix,
2347 );
2348 const clen = (
2349 (is_pure_suffixes_only_node ? 3 : 4) + // lengths of children and data
2350 dlen +
2351 cpnodes.length + csnodes.length +
2352 cpbranches.length + csbranches.length +
2353 whole.consumed_len_bytes +
2354 suffix.consumed_len_bytes
2355 );
2356 if (canonical.length < clen) {
2357 canonical = new Uint8Array(clen);
2358 }
2359 let ci = 0;
2360 canonical[ci] = is_pure_suffixes_only_node ? 1 : 0;
2361 ci += 1;
2362 canonical[ci] = dlen;
2363 ci += 1;
2364 canonical.set(data, ci);
2365 ci += dlen;
2366 canonical[ci] = input[coffset];
2367 ci += 1;
2368 if (!is_pure_suffixes_only_node) {
2369 canonical[ci] = input[coffset + 1];
2370 ci += 1;
2371 }
2372 canonical.set(cpnodes, ci);
2373 ci += cpnodes.length;
2374 canonical.set(csnodes, ci);
2375 ci += csnodes.length;
2376 canonical.set(cpbranches, ci);
2377 ci += cpbranches.length;
2378 canonical.set(csbranches, ci);
2379 ci += csbranches.length;
2380 const leavesOffset = i - whole.consumed_len_bytes - suffix.consumed_len_bytes;
2381 for (let j = leavesOffset; j < i; j += 1) {
2382 canonical[ci + j - leavesOffset] = input[j];
2383 }
2384 siphashOfBytes(canonical.subarray(0, clen), 0, 0, 0, 0, hash);
2385 hash[2] &= 0x7f;
2386 } else {
2387 // uncompressed node
2388 const dlen = input [i + 1];
2389 i += 2;
2390 if (dlen === 0) {
2391 data = EMPTY_UINT8;
2392 } else {
2393 data = new Uint8Array(input.buffer, i + input.byteOffset, dlen);
2394 }
2395 i += dlen;
2396 const {
2397 consumed_len_bytes: branches_consumed_len_bytes,
2398 branches,
2399 might_have_prefix_branches,
2400 } = makeBranchesFromBinaryData(input, i, compression_tag);
2401 i += branches_consumed_len_bytes;
2402 let whole;
2403 let suffix;
2404 if (is_pure_suffixes_only_node) {
2405 whole = EMPTY_BITMAP;
2406 suffix = input[i] === 0 ?
2407 EMPTY_BITMAP1 :
2408 new RoaringBitmap(input, i);
2409 i += suffix.consumed_len_bytes;
2410 } else if (input[i] === 0xff) {
2411 whole = EMPTY_BITMAP;
2412 suffix = EMPTY_BITMAP;
2413 i += 1;
2414 } else {
2415 whole = input[i] === 0 ?
2416 EMPTY_BITMAP1 :
2417 new RoaringBitmap(input, i);
2418 i += whole.consumed_len_bytes;
2419 suffix = input[i] === 0 ?
2420 EMPTY_BITMAP1 :
2421 new RoaringBitmap(input, i);
2422 i += suffix.consumed_len_bytes;
2423 }
2424 siphashOfBytes(new Uint8Array(
2425 input.buffer,
2426 start + input.byteOffset,
2427 i - start,
2428 ), 0, 0, 0, 0, hash);
2429 hash[2] &= 0x7f;
2430 tree = new SearchTree(
2431 branches,
2432 might_have_prefix_branches,
2433 data,
2434 whole,
2435 suffix,
2436 );
2437 }
2438 hash_history.push({hash: truncatedHash.slice(), used: false});
2439 if (data.length !== 0) {
2440 data_history.push(data);
2441 }
2442 const tree_branch_nodeids = tree.branches.nodeids;
2443 const tree_branch_subtrees = tree.branches.subtrees;
2444 let j = 0;
2445 let lb = tree.branches.subtrees.length;
2446 while (j < lb) {
2447 // node id with a 1 in its most significant bit is inlined, and, so
2448 // it won't be in the stash
2449 if ((tree_branch_nodeids[j * 6] & 0x80) === 0) {
2450 const subtree = stash.getWithOffsetKey(tree_branch_nodeids, j * 6);
2451 if (subtree !== undefined) {
2452 tree_branch_subtrees[j] = Promise.resolve(subtree);
2453 }
2454 }
2455 j += 1;
2456 }
2457 const tree_mhp_branch_nodeids = tree.might_have_prefix_branches.nodeids;
2458 const tree_mhp_branch_subtrees = tree.might_have_prefix_branches.subtrees;
2459 j = 0;
2460 lb = tree.might_have_prefix_branches.subtrees.length;
2461 while (j < lb) {
2462 // node id with a 1 in its most significant bit is inlined, and, so
2463 // it won't be in the stash
2464 if ((tree_mhp_branch_nodeids[j * 6] & 0x80) === 0) {
2465 const subtree = stash.getWithOffsetKey(tree_mhp_branch_nodeids, j * 6);
2466 if (subtree !== undefined) {
2467 tree_mhp_branch_subtrees[j] = Promise.resolve(subtree);
2468 }
2469 }
2470 j += 1;
2471 }
2472 if (i !== l) {
2473 stash.set(truncatedHash, tree);
2474 }
2475 }
2476 return [truncatedHash, tree];
2477 }
2478
2479 return new Promise((resolve, reject) => {
2480 registry.searchTreeRootCallback = (error, data) => {
2481 if (data) {
2482 resolve(data);
2483 } else {
2484 reject(error);
2485 }
2486 };
2487 hooks.loadRoot(callbacks);
2488 });
2489}
2490
2491if (typeof window !== "undefined") {
2492 window.Stringdex = {
2493 loadDatabase,
2494 };
2495 window.RoaringBitmap = RoaringBitmap;
2496 if (window.StringdexOnload) {
2497 window.StringdexOnload.forEach(cb => cb(window.Stringdex));
2498 }
2499} else {
2500 /** @type {stringdex.Stringdex} */
2501 // eslint-disable-next-line no-undef
2502 module.exports.Stringdex = {
2503 loadDatabase,
2504 };
2505 /** @type {stringdex.RoaringBitmap} */
2506 // eslint-disable-next-line no-undef
2507 module.exports.RoaringBitmap = RoaringBitmap;
2508}
2509
2510// eslint-disable-next-line max-len
2511// polyfill https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array/fromBase64
2512/**
2513 * @type {function(string): Uint8Array} base64
2514 */
2515//@ts-expect-error
2516const makeUint8ArrayFromBase64 = Uint8Array.fromBase64 ? Uint8Array.fromBase64 : (string => {
2517 const bytes_as_string = atob(string);
2518 const l = bytes_as_string.length;
2519 const bytes = new Uint8Array(l);
2520 for (let i = 0; i < l; ++i) {
2521 bytes[i] = bytes_as_string.charCodeAt(i);
2522 }
2523 return bytes;
2524});
2525/**
2526 * @type {function(string): Uint8Array} base64
2527 */
2528//@ts-expect-error
2529const makeUint8ArrayFromHex = Uint8Array.fromHex ? Uint8Array.fromHex : (string => {
2530 /** @type {Object<string, number>} */
2531 const alpha = {
2532 "0": 0, "1": 1,
2533 "2": 2, "3": 3,
2534 "4": 4, "5": 5,
2535 "6": 6, "7": 7,
2536 "8": 8, "9": 9,
2537 "a": 10, "b": 11,
2538 "A": 10, "B": 11,
2539 "c": 12, "d": 13,
2540 "C": 12, "D": 13,
2541 "e": 14, "f": 15,
2542 "E": 14, "F": 15,
2543 };
2544 const l = string.length >> 1;
2545 const bytes = new Uint8Array(l);
2546 for (let i = 0; i < l; i += 1) {
2547 const top = string[i << 1];
2548 const bottom = string[(i << 1) + 1];
2549 bytes[i] = (alpha[top] << 4) | alpha[bottom];
2550 }
2551 return bytes;
2552});
2553
2554/**
2555 * @type {function(Uint8Array): string} base64
2556 */
2557//@ts-expect-error
2558const makeHexFromUint8Array = Uint8Array.prototype.toHex ? (array => array.toHex()) : (array => {
2559 /** @type {string[]} */
2560 const alpha = [
2561 "0", "1",
2562 "2", "3",
2563 "4", "5",
2564 "6", "7",
2565 "8", "9",
2566 "a", "b",
2567 "c", "d",
2568 "e", "f",
2569 ];
2570 const l = array.length;
2571 const v = [];
2572 for (let i = 0; i < l; ++i) {
2573 v.push(alpha[array[i] >> 4]);
2574 v.push(alpha[array[i] & 0xf]);
2575 }
2576 return v.join("");
2577});
2578
2579//////////////
2580
2581/**
2582 * SipHash 1-3
2583 * @param {Uint8Array} input data to be hashed; all codepoints in string should be less than 256
2584 * @param {number} k0lo first word of key
2585 * @param {number} k0hi second word of key
2586 * @param {number} k1lo third word of key
2587 * @param {number} k1hi fourth word of key
2588 * @param {Uint8Array} output the data to write (clobber the first eight bytes)
2589 */
2590function siphashOfBytes(input, k0lo, k0hi, k1lo, k1hi, output) {
2591 // hash state
2592 // While siphash uses 64 bit state, js only has native support
2593 // for 32 bit numbers. BigInt, unfortunately, doesn't count.
2594 // It's too slow.
2595 let v0lo = k0lo ^ 0x70736575;
2596 let v0hi = k0hi ^ 0x736f6d65;
2597 let v1lo = k1lo ^ 0x6e646f6d;
2598 let v1hi = k1hi ^ 0x646f7261;
2599 let v2lo = k0lo ^ 0x6e657261;
2600 let v2hi = k0hi ^ 0x6c796765;
2601 let v3lo = k1lo ^ 0x79746573;
2602 let v3hi = k1hi ^ 0x74656462;
2603 const inputLength = input.length;
2604 let inputI = 0;
2605 // main hash loop
2606 const left = inputLength & 0x7;
2607 let milo = 0;
2608 let mihi = 0;
2609 while (inputI < inputLength - left) {
2610 u8ToU64le(inputI, inputI + 8);
2611 v3lo ^= milo;
2612 v3hi ^= mihi;
2613 siphashCompress();
2614 v0lo ^= milo;
2615 v0hi ^= mihi;
2616 inputI += 8;
2617 }
2618 u8ToU64le(inputI, inputI + left);
2619 // finish
2620 const blo = milo;
2621 const bhi = ((inputLength & 0xff) << 24) | mihi;
2622 v3lo ^= blo;
2623 v3hi ^= bhi;
2624 siphashCompress();
2625 v0lo ^= blo;
2626 v0hi ^= bhi;
2627 v2lo ^= 0xff;
2628 siphashCompress();
2629 siphashCompress();
2630 siphashCompress();
2631 output[7] = (v0lo ^ v1lo ^ v2lo ^ v3lo) & 0xff;
2632 output[6] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 8;
2633 output[5] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 16;
2634 output[4] = (v0lo ^ v1lo ^ v2lo ^ v3lo) >>> 24;
2635 output[3] = (v0hi ^ v1hi ^ v2hi ^ v3hi) & 0xff;
2636 output[2] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 8;
2637 output[1] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 16;
2638 output[0] = (v0hi ^ v1hi ^ v2hi ^ v3hi) >>> 24;
2639 /**
2640 * Convert eight bytes to a single 64-bit number
2641 * @param {number} offset
2642 * @param {number} length
2643 */
2644 function u8ToU64le(offset, length) {
2645 const n0 = offset < length ? input[offset] & 0xff : 0;
2646 const n1 = offset + 1 < length ? input[offset + 1] & 0xff : 0;
2647 const n2 = offset + 2 < length ? input[offset + 2] & 0xff : 0;
2648 const n3 = offset + 3 < length ? input[offset + 3] & 0xff : 0;
2649 const n4 = offset + 4 < length ? input[offset + 4] & 0xff : 0;
2650 const n5 = offset + 5 < length ? input[offset + 5] & 0xff : 0;
2651 const n6 = offset + 6 < length ? input[offset + 6] & 0xff : 0;
2652 const n7 = offset + 7 < length ? input[offset + 7] & 0xff : 0;
2653 milo = n0 | (n1 << 8) | (n2 << 16) | (n3 << 24);
2654 mihi = n4 | (n5 << 8) | (n6 << 16) | (n7 << 24);
2655 }
2656 function siphashCompress() {
2657 // v0 += v1;
2658 v0hi = (v0hi + v1hi + (((v0lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
2659 v0lo = (v0lo + v1lo) | 0;
2660 // rotl(v1, 13)
2661 let v1lo_ = v1lo;
2662 let v1hi_ = v1hi;
2663 v1lo = (v1lo_ << 13) | (v1hi_ >>> 19);
2664 v1hi = (v1hi_ << 13) | (v1lo_ >>> 19);
2665 // v1 ^= v0
2666 v1lo ^= v0lo;
2667 v1hi ^= v0hi;
2668 // rotl(v0, 32)
2669 const v0lo_ = v0lo;
2670 const v0hi_ = v0hi;
2671 v0lo = v0hi_;
2672 v0hi = v0lo_;
2673 // v2 += v3
2674 v2hi = (v2hi + v3hi + (((v2lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
2675 v2lo = (v2lo + v3lo) | 0;
2676 // rotl(v3, 16)
2677 let v3lo_ = v3lo;
2678 let v3hi_ = v3hi;
2679 v3lo = (v3lo_ << 16) | (v3hi_ >>> 16);
2680 v3hi = (v3hi_ << 16) | (v3lo_ >>> 16);
2681 // v3 ^= v2
2682 v3lo ^= v2lo;
2683 v3hi ^= v2hi;
2684 // v0 += v3
2685 v0hi = (v0hi + v3hi + (((v0lo >>> 0) + (v3lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
2686 v0lo = (v0lo + v3lo) | 0;
2687 // rotl(v3, 21)
2688 v3lo_ = v3lo;
2689 v3hi_ = v3hi;
2690 v3lo = (v3lo_ << 21) | (v3hi_ >>> 11);
2691 v3hi = (v3hi_ << 21) | (v3lo_ >>> 11);
2692 // v3 ^= v0
2693 v3lo ^= v0lo;
2694 v3hi ^= v0hi;
2695 // v2 += v1
2696 v2hi = (v2hi + v1hi + (((v2lo >>> 0) + (v1lo >>> 0) > 0xffffffff) ? 1 : 0)) | 0;
2697 v2lo = (v2lo + v1lo) | 0;
2698 // rotl(v1, 17)
2699 v1lo_ = v1lo;
2700 v1hi_ = v1hi;
2701 v1lo = (v1lo_ << 17) | (v1hi_ >>> 15);
2702 v1hi = (v1hi_ << 17) | (v1lo_ >>> 15);
2703 // v1 ^= v2
2704 v1lo ^= v2lo;
2705 v1hi ^= v2hi;
2706 // rotl(v2, 32)
2707 const v2lo_ = v2lo;
2708 const v2hi_ = v2hi;
2709 v2lo = v2hi_;
2710 v2hi = v2lo_;
2711 }
2712}
2713
2714//////////////
2715
2716
2717// Parts of this code are based on Lucene, which is licensed under the
2718// Apache/2.0 license.
2719// More information found here:
2720// https://fossies.org/linux/lucene/lucene/core/src/java/org/apache/lucene/util/automaton/
2721// LevenshteinAutomata.java
2722class ParametricDescription {
2723 /**
2724 * @param {number} w
2725 * @param {number} n
2726 * @param {Int32Array} minErrors
2727 */
2728 constructor(w, n, minErrors) {
2729 this.w = w;
2730 this.n = n;
2731 this.minErrors = minErrors;
2732 }
2733 /**
2734 * @param {number} absState
2735 * @returns {boolean}
2736 */
2737 isAccept(absState) {
2738 const state = Math.floor(absState / (this.w + 1));
2739 const offset = absState % (this.w + 1);
2740 return this.w - offset + this.minErrors[state] <= this.n;
2741 }
2742 /**
2743 * @param {number} absState
2744 * @returns {number}
2745 */
2746 getPosition(absState) {
2747 return absState % (this.w + 1);
2748 }
2749 /**
2750 * @param {Uint8Array} name
2751 * @param {number} charCode
2752 * @param {number} pos
2753 * @param {number} end
2754 * @returns {number}
2755 */
2756 getVector(name, charCode, pos, end) {
2757 let vector = 0;
2758 for (let i = pos; i < end; i += 1) {
2759 vector = vector << 1;
2760 if (name[i] === charCode) {
2761 vector |= 1;
2762 }
2763 }
2764 return vector;
2765 }
2766 /**
2767 * @param {Int32Array} data
2768 * @param {number} index
2769 * @param {number} bitsPerValue
2770 * @returns {number}
2771 */
2772 unpack(data, index, bitsPerValue) {
2773 const bitLoc = (bitsPerValue * index);
2774 const dataLoc = bitLoc >> 5;
2775 const bitStart = bitLoc & 31;
2776 if (bitStart + bitsPerValue <= 32) {
2777 // not split
2778 return ((data[dataLoc] >> bitStart) & this.MASKS[bitsPerValue - 1]);
2779 } else {
2780 // split
2781 const part = 32 - bitStart;
2782 return ~~(((data[dataLoc] >> bitStart) & this.MASKS[part - 1]) +
2783 ((data[1 + dataLoc] & this.MASKS[bitsPerValue - part - 1]) << part));
2784 }
2785 }
2786}
2787ParametricDescription.prototype.MASKS = new Int32Array([
2788 0x1, 0x3, 0x7, 0xF,
2789 0x1F, 0x3F, 0x7F, 0xFF,
2790 0x1FF, 0x3F, 0x7FF, 0xFFF,
2791 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF,
2792 0x1FFFF, 0x3FFFF, 0x7FFFF, 0xFFFFF,
2793 0x1FFFFF, 0x3FFFFF, 0x7FFFFF, 0xFFFFFF,
2794 0x1FFFFFF, 0x3FFFFFF, 0x7FFFFFF, 0xFFFFFFF,
2795 0x1FFFFFFF, 0x3FFFFFFF, 0x7FFFFFFF, 0xFFFFFFFF,
2796]);
2797
2798// The following code was generated with the moman/finenight pkg
2799// This package is available under the MIT License, see NOTICE.txt
2800// for more details.
2801// This class is auto-generated, Please do not modify it directly.
2802// You should modify the https://gitlab.com/notriddle/createAutomata.py instead.
2803// The following code was generated with the moman/finenight pkg
2804// This package is available under the MIT License, see NOTICE.txt
2805// for more details.
2806// This class is auto-generated, Please do not modify it directly.
2807// You should modify https://gitlab.com/notriddle/moman-rustdoc instead.
2808
2809class Lev2TParametricDescription extends ParametricDescription {
2810 /**
2811 * @param {number} absState
2812 * @param {number} position
2813 * @param {number} vector
2814 * @returns {number}
2815 */
2816 transition(absState, position, vector) {
2817 let state = Math.floor(absState / (this.w + 1));
2818 let offset = absState % (this.w + 1);
2819
2820 if (position === this.w) {
2821 if (state < 3) {
2822 const loc = Math.imul(vector, 3) + state;
2823 offset += this.unpack(this.offsetIncrs0, loc, 1);
2824 state = this.unpack(this.toStates0, loc, 2) - 1;
2825 }
2826 } else if (position === this.w - 1) {
2827 if (state < 5) {
2828 const loc = Math.imul(vector, 5) + state;
2829 offset += this.unpack(this.offsetIncrs1, loc, 1);
2830 state = this.unpack(this.toStates1, loc, 3) - 1;
2831 }
2832 } else if (position === this.w - 2) {
2833 if (state < 13) {
2834 const loc = Math.imul(vector, 13) + state;
2835 offset += this.unpack(this.offsetIncrs2, loc, 2);
2836 state = this.unpack(this.toStates2, loc, 4) - 1;
2837 }
2838 } else if (position === this.w - 3) {
2839 if (state < 28) {
2840 const loc = Math.imul(vector, 28) + state;
2841 offset += this.unpack(this.offsetIncrs3, loc, 2);
2842 state = this.unpack(this.toStates3, loc, 5) - 1;
2843 }
2844 } else if (position === this.w - 4) {
2845 if (state < 45) {
2846 const loc = Math.imul(vector, 45) + state;
2847 offset += this.unpack(this.offsetIncrs4, loc, 3);
2848 state = this.unpack(this.toStates4, loc, 6) - 1;
2849 }
2850 } else {
2851 // eslint-disable-next-line no-lonely-if
2852 if (state < 45) {
2853 const loc = Math.imul(vector, 45) + state;
2854 offset += this.unpack(this.offsetIncrs5, loc, 3);
2855 state = this.unpack(this.toStates5, loc, 6) - 1;
2856 }
2857 }
2858
2859 if (state === -1) {
2860 // null state
2861 return -1;
2862 } else {
2863 // translate back to abs
2864 return Math.imul(state, this.w + 1) + offset;
2865 }
2866 }
2867
2868 // state map
2869 // 0 -> [(0, 0)]
2870 // 1 -> [(0, 1)]
2871 // 2 -> [(0, 2)]
2872 // 3 -> [(0, 1), (1, 1)]
2873 // 4 -> [(0, 2), (1, 2)]
2874 // 5 -> [(0, 1), (1, 1), (2, 1)]
2875 // 6 -> [(0, 2), (1, 2), (2, 2)]
2876 // 7 -> [(0, 1), (2, 1)]
2877 // 8 -> [(0, 1), (2, 2)]
2878 // 9 -> [(0, 2), (2, 1)]
2879 // 10 -> [(0, 2), (2, 2)]
2880 // 11 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
2881 // 12 -> [t(0, 2), (0, 2), (1, 2), (2, 2)]
2882 // 13 -> [(0, 2), (1, 2), (2, 2), (3, 2)]
2883 // 14 -> [(0, 1), (1, 1), (3, 2)]
2884 // 15 -> [(0, 1), (2, 2), (3, 2)]
2885 // 16 -> [(0, 1), (3, 2)]
2886 // 17 -> [(0, 1), t(1, 2), (2, 2), (3, 2)]
2887 // 18 -> [(0, 2), (1, 2), (3, 1)]
2888 // 19 -> [(0, 2), (1, 2), (3, 2)]
2889 // 20 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2)]
2890 // 21 -> [(0, 2), (2, 1), (3, 1)]
2891 // 22 -> [(0, 2), (2, 2), (3, 2)]
2892 // 23 -> [(0, 2), (3, 1)]
2893 // 24 -> [(0, 2), (3, 2)]
2894 // 25 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2)]
2895 // 26 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2)]
2896 // 27 -> [t(0, 2), (0, 2), (1, 2), (3, 1)]
2897 // 28 -> [(0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
2898 // 29 -> [(0, 2), (1, 2), (2, 2), (4, 2)]
2899 // 30 -> [(0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
2900 // 31 -> [(0, 2), (1, 2), (3, 2), (4, 2)]
2901 // 32 -> [(0, 2), (1, 2), (4, 2)]
2902 // 33 -> [(0, 2), (1, 2), t(1, 2), (2, 2), (3, 2), (4, 2)]
2903 // 34 -> [(0, 2), (1, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
2904 // 35 -> [(0, 2), (2, 1), (4, 2)]
2905 // 36 -> [(0, 2), (2, 2), (3, 2), (4, 2)]
2906 // 37 -> [(0, 2), (2, 2), (4, 2)]
2907 // 38 -> [(0, 2), (3, 2), (4, 2)]
2908 // 39 -> [(0, 2), (4, 2)]
2909 // 40 -> [(0, 2), t(1, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
2910 // 41 -> [(0, 2), t(2, 2), (2, 2), (3, 2), (4, 2)]
2911 // 42 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (3, 2), (4, 2)]
2912 // 43 -> [t(0, 2), (0, 2), (1, 2), (2, 2), (4, 2)]
2913 // 44 -> [t(0, 2), (0, 2), (1, 2), (2, 2), t(2, 2), (3, 2), (4, 2)]
2914
2915
2916 /** @param {number} w - length of word being checked */
2917 constructor(w) {
2918 super(w, 2, new Int32Array([
2919 0,1,2,0,1,-1,0,-1,0,-1,0,-1,0,-1,-1,-1,-1,-1,-2,-1,-1,-2,-1,-2,
2920 -1,-1,-1,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,-2,
2921 ]));
2922 }
2923}
2924
2925Lev2TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
2926 0xe,
2927]);
2928Lev2TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
2929 0x0,
2930]);
2931
2932Lev2TParametricDescription.prototype.toStates1 = /*3 bits per value */ new Int32Array([
2933 0x1a688a2c,
2934]);
2935Lev2TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
2936 0x3e0,
2937]);
2938
2939Lev2TParametricDescription.prototype.toStates2 = /*4 bits per value */ new Int32Array([
2940 0x70707054,0xdc07035,0x3dd3a3a,0x2323213a,
2941 0x15435223,0x22545432,0x5435,
2942]);
2943Lev2TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
2944 0x80000,0x55582088,0x55555555,0x55,
2945]);
2946
2947Lev2TParametricDescription.prototype.toStates3 = /*5 bits per value */ new Int32Array([
2948 0x1c0380a4,0x700a570,0xca529c0,0x180a00,
2949 0xa80af180,0xc5498e60,0x5a546398,0x8c4300e8,
2950 0xac18c601,0xd8d43501,0x863500ad,0x51976d6a,
2951 0x8ca0180a,0xc3501ac2,0xb0c5be16,0x76dda8a5,
2952 0x18c4519,0xc41294a,0xe248d231,0x1086520c,
2953 0xce31ac42,0x13946358,0x2d0348c4,0x6732d494,
2954 0x1ad224a5,0xd635ad4b,0x520c4139,0xce24948,
2955 0x22110a52,0x58ce729d,0xc41394e3,0x941cc520,
2956 0x90e732d4,0x4729d224,0x39ce35ad,
2957]);
2958Lev2TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
2959 0x80000,0xc0c830,0x300f3c30,0x2200fcff,
2960 0xcaa00a08,0x3c2200a8,0xa8fea00a,0x55555555,
2961 0x55555555,0x55555555,0x55555555,0x55555555,
2962 0x55555555,0x55555555,
2963]);
2964
2965Lev2TParametricDescription.prototype.toStates4 = /*6 bits per value */ new Int32Array([
2966 0x801c0144,0x1453803,0x14700038,0xc0005145,
2967 0x1401,0x14,0x140000,0x0,
2968 0x510000,0x6301f007,0x301f00d1,0xa186178,
2969 0xc20ca0c3,0xc20c30,0xc30030c,0xc00c00cd,
2970 0xf0c00c30,0x4c054014,0xc30944c3,0x55150c34,
2971 0x8300550,0x430c0143,0x50c31,0xc30850c,
2972 0xc3143000,0x50053c50,0x5130d301,0x850d30c2,
2973 0x30a08608,0xc214414,0x43142145,0x21450031,
2974 0x1400c314,0x4c143145,0x32832803,0x28014d6c,
2975 0xcd34a0c3,0x1c50c76,0x1c314014,0x430c30c3,
2976 0x1431,0xc300500,0xca00d303,0xd36d0e40,
2977 0x90b0e400,0xcb2abb2c,0x70c20ca1,0x2c32ca2c,
2978 0xcd2c70cb,0x31c00c00,0x34c2c32c,0x5583280,
2979 0x558309b7,0x6cd6ca14,0x430850c7,0x51c51401,
2980 0x1430c714,0xc3087,0x71451450,0xca00d30,
2981 0xc26dc156,0xb9071560,0x1cb2abb2,0xc70c2144,
2982 0xb1c51ca1,0x1421c70c,0xc51c00c3,0x30811c51,
2983 0x24324308,0xc51031c2,0x70820820,0x5c33830d,
2984 0xc33850c3,0x30c30c30,0xc30c31c,0x451450c3,
2985 0x20c20c20,0xda0920d,0x5145914f,0x36596114,
2986 0x51965865,0xd9643653,0x365a6590,0x51964364,
2987 0x43081505,0x920b2032,0x2c718b28,0xd7242249,
2988 0x35cb28b0,0x2cb3872c,0x972c30d7,0xb0c32cb2,
2989 0x4e1c75c,0xc80c90c2,0x62ca2482,0x4504171c,
2990 0xd65d9610,0x33976585,0xd95cb5d,0x4b5ca5d7,
2991 0x73975c36,0x10308138,0xc2245105,0x41451031,
2992 0x14e24208,0xc35c3387,0x51453851,0x1c51c514,
2993 0xc70c30c3,0x20451450,0x14f1440c,0x4f0da092,
2994 0x4513d41,0x6533944d,0x1350e658,0xe1545055,
2995 0x64365a50,0x5519383,0x51030815,0x28920718,
2996 0x441c718b,0x714e2422,0x1c35cb28,0x4e1c7387,
2997 0xb28e1c51,0x5c70c32c,0xc204e1c7,0x81c61440,
2998 0x1c62ca24,0xd04503ce,0x85d63944,0x39338e65,
2999 0x8e154387,0x364b5ca3,0x38739738,
3000]);
3001Lev2TParametricDescription.prototype.offsetIncrs4 = /*3 bits per value */ new Int32Array([
3002 0x10000000,0xc00000,0x60061,0x400,
3003 0x0,0x80010008,0x249248a4,0x8229048,
3004 0x2092,0x6c3603,0xb61b6c30,0x6db6036d,
3005 0xdb6c0,0x361b0180,0x91b72000,0xdb11b71b,
3006 0x6db6236,0x1008200,0x12480012,0x24924906,
3007 0x48200049,0x80410002,0x24000900,0x4924a489,
3008 0x10822492,0x20800125,0x48360,0x9241b692,
3009 0x6da4924,0x40009268,0x241b010,0x291b4900,
3010 0x6d249249,0x49493423,0x92492492,0x24924924,
3011 0x49249249,0x92492492,0x24924924,0x49249249,
3012 0x92492492,0x24924924,0x49249249,0x92492492,
3013 0x24924924,0x49249249,0x92492492,0x24924924,
3014 0x49249249,0x92492492,0x24924924,0x49249249,
3015 0x92492492,0x24924924,0x49249249,0x92492492,
3016 0x24924924,0x49249249,0x92492492,0x24924924,
3017 0x49249249,0x92492492,0x24924924,0x49249249,
3018 0x92492492,0x24924924,0x49249249,0x2492,
3019]);
3020
3021Lev2TParametricDescription.prototype.toStates5 = /*6 bits per value */ new Int32Array([
3022 0x801c0144,0x1453803,0x14700038,0xc0005145,
3023 0x1401,0x14,0x140000,0x0,
3024 0x510000,0x4e00e007,0xe0051,0x3451451c,
3025 0xd015000,0x30cd0000,0xc30c30c,0xc30c30d4,
3026 0x40c30c30,0x7c01c014,0xc03458c0,0x185e0c07,
3027 0x2830c286,0x830c3083,0xc30030,0x33430c,
3028 0x30c3003,0x70051030,0x16301f00,0x8301f00d,
3029 0x30a18617,0xc20ca0c,0x431420c3,0xb1450c51,
3030 0x14314315,0x4f143145,0x34c05401,0x4c30944c,
3031 0x55150c3,0x30830055,0x1430c014,0xc00050c3,
3032 0xc30850,0xc314300,0x150053c5,0x25130d30,
3033 0x5430d30c,0xc0354154,0x300d0c90,0x1cb2cd0c,
3034 0xc91cb0c3,0x72c30cb2,0x14f1cb2c,0xc34c0540,
3035 0x34c30944,0x82182214,0x851050c2,0x50851430,
3036 0x1400c50c,0x30c5085,0x50c51450,0x150053c,
3037 0xc25130d3,0x8850d30,0x1430a086,0x450c2144,
3038 0x51cb1c21,0x1c91c70c,0xc71c314b,0x34c1cb1,
3039 0x6c328328,0xc328014d,0x76cd34a0,0x1401c50c,
3040 0xc31c3140,0x31430c30,0x14,0x30c3005,
3041 0xa0ca00d3,0x535b0c,0x4d2830ca,0x514369b3,
3042 0xc500d01,0x5965965a,0x30d46546,0x6435030c,
3043 0x8034c659,0xdb439032,0x2c390034,0xcaaecb24,
3044 0x30832872,0xcb28b1c,0x4b1c32cb,0x70030033,
3045 0x30b0cb0c,0xe40ca00d,0x400d36d0,0xb2c90b0e,
3046 0xca1cb2ab,0xa2c70c20,0x6575d95c,0x4315b5ce,
3047 0x95c53831,0x28034c5d,0x9b705583,0xa1455830,
3048 0xc76cd6c,0x40143085,0x71451c51,0x871430c,
3049 0x450000c3,0xd3071451,0x1560ca00,0x560c26dc,
3050 0xb35b2851,0xc914369,0x1a14500d,0x46593945,
3051 0xcb2c939,0x94507503,0x328034c3,0x9b70558,
3052 0xe41c5583,0x72caaeca,0x1c308510,0xc7147287,
3053 0x50871c32,0x1470030c,0xd307147,0xc1560ca0,
3054 0x1560c26d,0xabb2b907,0x21441cb2,0x38a1c70c,
3055 0x8e657394,0x314b1c93,0x39438738,0x43083081,
3056 0x31c22432,0x820c510,0x830d7082,0x50c35c33,
3057 0xc30c338,0xc31c30c3,0x50c30c30,0xc204514,
3058 0x890c90c2,0x31440c70,0xa8208208,0xea0df0c3,
3059 0x8a231430,0xa28a28a2,0x28a28a1e,0x1861868a,
3060 0x48308308,0xc3682483,0x14516453,0x4d965845,
3061 0xd4659619,0x36590d94,0xd969964,0x546590d9,
3062 0x20c20541,0x920d20c,0x5914f0da,0x96114514,
3063 0x65865365,0xe89d3519,0x99e7a279,0x9e89e89e,
3064 0x81821827,0xb2032430,0x18b28920,0x422492c7,
3065 0xb28b0d72,0x3872c35c,0xc30d72cb,0x32cb2972,
3066 0x1c75cb0c,0xc90c204e,0xa2482c80,0x24b1c62c,
3067 0xc3a89089,0xb0ea2e42,0x9669a31c,0xa4966a28,
3068 0x59a8a269,0x8175e7a,0xb203243,0x718b2892,
3069 0x4114105c,0x17597658,0x74ce5d96,0x5c36572d,
3070 0xd92d7297,0xe1ce5d70,0xc90c204,0xca2482c8,
3071 0x4171c62,0x5d961045,0x976585d6,0x79669533,
3072 0x964965a2,0x659689e6,0x308175e7,0x24510510,
3073 0x451031c2,0xe2420841,0x5c338714,0x453851c3,
3074 0x51c51451,0xc30c31c,0x451450c7,0x41440c20,
3075 0xc708914,0x82105144,0xf1c58c90,0x1470ea0d,
3076 0x61861863,0x8a1e85e8,0x8687a8a2,0x3081861,
3077 0x24853c51,0x5053c368,0x1341144f,0x96194ce5,
3078 0x1544d439,0x94385514,0xe0d90d96,0x5415464,
3079 0x4f1440c2,0xf0da0921,0x4513d414,0x533944d0,
3080 0x350e6586,0x86082181,0xe89e981d,0x18277689,
3081 0x10308182,0x89207185,0x41c718b2,0x14e24224,
3082 0xc35cb287,0xe1c73871,0x28e1c514,0xc70c32cb,
3083 0x204e1c75,0x1c61440c,0xc62ca248,0x90891071,
3084 0x2e41c58c,0xa31c70ea,0xe86175e7,0xa269a475,
3085 0x5e7a57a8,0x51030817,0x28920718,0xf38718b,
3086 0xe5134114,0x39961758,0xe1ce4ce,0x728e3855,
3087 0x5ce0d92d,0xc204e1ce,0x81c61440,0x1c62ca24,
3088 0xd04503ce,0x85d63944,0x75338e65,0x5d86075e,
3089 0x89e69647,0x75e76576,
3090]);
3091Lev2TParametricDescription.prototype.offsetIncrs5 = /*3 bits per value */ new Int32Array([
3092 0x10000000,0xc00000,0x60061,0x400,
3093 0x0,0x60000008,0x6b003080,0xdb6ab6db,
3094 0x2db6,0x800400,0x49245240,0x11482412,
3095 0x104904,0x40020000,0x92292000,0xa4b25924,
3096 0x9649658,0xd80c000,0xdb0c001b,0x80db6d86,
3097 0x6db01b6d,0xc0600003,0x86000d86,0x6db6c36d,
3098 0xddadb6ed,0x300001b6,0x6c360,0xe37236e4,
3099 0x46db6236,0xdb6c,0x361b018,0xb91b7200,
3100 0x6dbb1b71,0x6db763,0x20100820,0x61248001,
3101 0x92492490,0x24820004,0x8041000,0x92400090,
3102 0x24924830,0x555b6a49,0x2080012,0x20004804,
3103 0x49252449,0x84112492,0x4000928,0x240201,
3104 0x92922490,0x58924924,0x49456,0x120d8082,
3105 0x6da4800,0x69249249,0x249a01b,0x6c04100,
3106 0x6d240009,0x92492483,0x24d5adb4,0x60208001,
3107 0x92000483,0x24925236,0x6846da49,0x10400092,
3108 0x241b0,0x49291b49,0x636d2492,0x92494935,
3109 0x24924924,0x49249249,0x92492492,0x24924924,
3110 0x49249249,0x92492492,0x24924924,0x49249249,
3111 0x92492492,0x24924924,0x49249249,0x92492492,
3112 0x24924924,0x49249249,0x92492492,0x24924924,
3113 0x49249249,0x92492492,0x24924924,0x49249249,
3114 0x92492492,0x24924924,0x49249249,0x92492492,
3115 0x24924924,0x49249249,0x92492492,0x24924924,
3116 0x49249249,0x92492492,0x24924924,0x49249249,
3117 0x92492492,0x24924924,0x49249249,0x92492492,
3118 0x24924924,0x49249249,0x92492492,0x24924924,
3119 0x49249249,0x92492492,0x24924924,0x49249249,
3120 0x92492492,0x24924924,0x49249249,0x92492492,
3121 0x24924924,0x49249249,0x92492492,0x24924924,
3122 0x49249249,0x92492492,0x24924924,0x49249249,
3123 0x92492492,0x24924924,0x49249249,0x92492492,
3124 0x24924924,0x49249249,0x92492492,0x24924924,
3125 0x49249249,0x92492492,0x24924924,
3126]);
3127
3128class Lev1TParametricDescription extends ParametricDescription {
3129 /**
3130 * @param {number} absState
3131 * @param {number} position
3132 * @param {number} vector
3133 * @returns {number}
3134 */
3135 transition(absState, position, vector) {
3136 let state = Math.floor(absState / (this.w + 1));
3137 let offset = absState % (this.w + 1);
3138
3139 if (position === this.w) {
3140 if (state < 2) {
3141 const loc = Math.imul(vector, 2) + state;
3142 offset += this.unpack(this.offsetIncrs0, loc, 1);
3143 state = this.unpack(this.toStates0, loc, 2) - 1;
3144 }
3145 } else if (position === this.w - 1) {
3146 if (state < 3) {
3147 const loc = Math.imul(vector, 3) + state;
3148 offset += this.unpack(this.offsetIncrs1, loc, 1);
3149 state = this.unpack(this.toStates1, loc, 2) - 1;
3150 }
3151 } else if (position === this.w - 2) {
3152 if (state < 6) {
3153 const loc = Math.imul(vector, 6) + state;
3154 offset += this.unpack(this.offsetIncrs2, loc, 2);
3155 state = this.unpack(this.toStates2, loc, 3) - 1;
3156 }
3157 } else {
3158 // eslint-disable-next-line no-lonely-if
3159 if (state < 6) {
3160 const loc = Math.imul(vector, 6) + state;
3161 offset += this.unpack(this.offsetIncrs3, loc, 2);
3162 state = this.unpack(this.toStates3, loc, 3) - 1;
3163 }
3164 }
3165
3166 if (state === -1) {
3167 // null state
3168 return -1;
3169 } else {
3170 // translate back to abs
3171 return Math.imul(state, this.w + 1) + offset;
3172 }
3173 }
3174
3175 // state map
3176 // 0 -> [(0, 0)]
3177 // 1 -> [(0, 1)]
3178 // 2 -> [(0, 1), (1, 1)]
3179 // 3 -> [(0, 1), (1, 1), (2, 1)]
3180 // 4 -> [(0, 1), (2, 1)]
3181 // 5 -> [t(0, 1), (0, 1), (1, 1), (2, 1)]
3182
3183
3184 /** @param {number} w - length of word being checked */
3185 constructor(w) {
3186 super(w, 1, new Int32Array([0,1,0,-1,-1,-1]));
3187 }
3188}
3189
3190Lev1TParametricDescription.prototype.toStates0 = /*2 bits per value */ new Int32Array([
3191 0x2,
3192]);
3193Lev1TParametricDescription.prototype.offsetIncrs0 = /*1 bits per value */ new Int32Array([
3194 0x0,
3195]);
3196
3197Lev1TParametricDescription.prototype.toStates1 = /*2 bits per value */ new Int32Array([
3198 0xa43,
3199]);
3200Lev1TParametricDescription.prototype.offsetIncrs1 = /*1 bits per value */ new Int32Array([
3201 0x38,
3202]);
3203
3204Lev1TParametricDescription.prototype.toStates2 = /*3 bits per value */ new Int32Array([
3205 0x12180003,0xb45a4914,0x69,
3206]);
3207Lev1TParametricDescription.prototype.offsetIncrs2 = /*2 bits per value */ new Int32Array([
3208 0x558a0000,0x5555,
3209]);
3210
3211Lev1TParametricDescription.prototype.toStates3 = /*3 bits per value */ new Int32Array([
3212 0x900c0003,0xa1904864,0x45a49169,0x5a6d196a,
3213 0x9634,
3214]);
3215Lev1TParametricDescription.prototype.offsetIncrs3 = /*2 bits per value */ new Int32Array([
3216 0xa0fc0000,0x5555ba08,0x55555555,
3217]);
src/librustdoc/html/static/js/tsconfig.json+1-1
......@@ -10,6 +10,6 @@
1010 "skipLibCheck": true
1111 },
1212 "typeAcquisition": {
13 "include": ["./rustdoc.d.ts"]
13 "include": ["./rustdoc.d.ts", "./stringdex.d.ts"]
1414 }
1515}
src/librustdoc/html/static_files.rs+1
......@@ -80,6 +80,7 @@ static_files! {
8080 normalize_css => "static/css/normalize.css",
8181 main_js => "static/js/main.js",
8282 search_js => "static/js/search.js",
83 stringdex_js => "static/js/stringdex.js",
8384 settings_js => "static/js/settings.js",
8485 src_script_js => "static/js/src-script.js",
8586 storage_js => "static/js/storage.js",
src/librustdoc/html/templates/page.html+4-15
......@@ -29,6 +29,7 @@
2929 data-rustdoc-version="{{rustdoc_version}}" {#+ #}
3030 data-channel="{{rust_channel}}" {#+ #}
3131 data-search-js="{{files.search_js}}" {#+ #}
32 data-stringdex-js="{{files.stringdex_js}}" {#+ #}
3233 data-settings-js="{{files.settings_js}}" {#+ #}
3334 > {# #}
3435 <script src="{{static_root_path|safe}}{{files.storage_js}}"></script>
......@@ -72,18 +73,9 @@
7273 <![endif]-->
7374 {{ layout.external_html.before_content|safe }}
7475 {% if page.css_class != "src" %}
75 <nav class="mobile-topbar"> {# #}
76 <button class="sidebar-menu-toggle" title="show sidebar"></button>
77 {% if !layout.logo.is_empty() || page.rust_logo %}
78 <a class="logo-container" href="{{page.root_path|safe}}{{display_krate_with_trailing_slash|safe}}index.html">
79 {% if page.rust_logo %}
80 <img class="rust-logo" src="{{static_root_path|safe}}{{files.rust_logo_svg}}" alt="">
81 {% else if !layout.logo.is_empty() %}
82 <img src="{{layout.logo}}" alt="">
83 {% endif %}
84 </a>
85 {% endif %}
86 </nav>
76 <rustdoc-topbar> {# #}
77 <h2><a href="#">{{page.short_title}}</a></h2> {# #}
78 </rustdoc-topbar>
8779 {% endif %}
8880 <nav class="sidebar">
8981 {% if page.css_class != "src" %}
......@@ -117,9 +109,6 @@
117109 <div class="sidebar-resizer" title="Drag to resize sidebar"></div> {# #}
118110 <main>
119111 {% if page.css_class != "src" %}<div class="width-limiter">{% endif %}
120 {# defined in storage.js to avoid duplicating complex UI across every page #}
121 {# and because the search form only works if JS is enabled anyway #}
122 <rustdoc-search></rustdoc-search> {# #}
123112 <section id="main-content" class="content">{{ content|safe }}</section>
124113 {% if page.css_class != "src" %}</div>{% endif %}
125114 </main>
src/librustdoc/html/templates/print_item.html+2-2
......@@ -12,8 +12,8 @@
1212 <h1>
1313 {{typ}}
1414 <span{% if item_type != "mod" +%} class="{{item_type}}"{% endif %}>
15 {{name}}
16 </span> {# #}
15 {{name|wrapped|safe}}
16 </span>&nbsp;{# #}
1717 <button id="copy-path" title="Copy item path to clipboard"> {# #}
1818 Copy item path {# #}
1919 </button> {# #}
src/tools/compiletest/src/runtest.rs+1-1
......@@ -2151,7 +2151,7 @@ impl<'test> TestCx<'test> {
21512151
21522152 #[rustfmt::skip]
21532153 let tidy_args = [
2154 "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar",
2154 "--new-blocklevel-tags", "rustdoc-search,rustdoc-toolbar,rustdoc-topbar",
21552155 "--indent", "yes",
21562156 "--indent-spaces", "2",
21572157 "--wrap", "0",
src/tools/html-checker/main.rs+1-1
......@@ -31,7 +31,7 @@ fn check_html_file(file: &Path) -> usize {
3131 .arg("--mute-id") // this option is useful in case we want to mute more warnings
3232 .arg("yes")
3333 .arg("--new-blocklevel-tags")
34 .arg("rustdoc-search,rustdoc-toolbar") // custom elements
34 .arg("rustdoc-search,rustdoc-toolbar,rustdoc-topbar") // custom elements
3535 .arg("--mute")
3636 .arg(&to_mute_s)
3737 .arg(file);
src/tools/rustdoc-js/tester.js+97-90
......@@ -1,7 +1,8 @@
11/* global globalThis */
2
23const fs = require("fs");
34const path = require("path");
4
5const { isGeneratorObject } = require("util/types");
56
67function arrayToCode(array) {
78 return array.map((value, index) => {
......@@ -45,23 +46,16 @@ function shouldIgnoreField(fieldName) {
4546}
4647
4748function valueMapper(key, testOutput) {
48 const isAlias = testOutput["is_alias"];
4949 let value = testOutput[key];
5050 // To make our life easier, if there is a "parent" type, we add it to the path.
5151 if (key === "path") {
52 if (testOutput["parent"] !== undefined) {
52 if (testOutput["parent"]) {
5353 if (value.length > 0) {
5454 value += "::" + testOutput["parent"]["name"];
5555 } else {
5656 value = testOutput["parent"]["name"];
5757 }
58 } else if (testOutput["is_alias"]) {
59 value = valueMapper(key, testOutput["original"]);
6058 }
61 } else if (isAlias && key === "alias") {
62 value = testOutput["name"];
63 } else if (isAlias && ["name"].includes(key)) {
64 value = testOutput["original"][key];
6559 }
6660 return value;
6761}
......@@ -237,7 +231,7 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
237231 const ignore_order = loadedFile.ignore_order;
238232 const exact_check = loadedFile.exact_check;
239233
240 const results = await doSearch(query, loadedFile.FILTER_CRATE);
234 const { resultsTable } = await doSearch(query, loadedFile.FILTER_CRATE);
241235 const error_text = [];
242236
243237 for (const key in expected) {
......@@ -247,37 +241,38 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
247241 if (!Object.prototype.hasOwnProperty.call(expected, key)) {
248242 continue;
249243 }
250 if (!Object.prototype.hasOwnProperty.call(results, key)) {
244 if (!Object.prototype.hasOwnProperty.call(resultsTable, key)) {
251245 error_text.push("==> Unknown key \"" + key + "\"");
252246 break;
253247 }
254248 const entry = expected[key];
255249
256 if (exact_check && entry.length !== results[key].length) {
250 if (exact_check && entry.length !== resultsTable[key].length) {
257251 error_text.push(queryName + "==> Expected exactly " + entry.length +
258 " results but found " + results[key].length + " in '" + key + "'");
252 " results but found " + resultsTable[key].length + " in '" + key + "'");
259253 }
260254
261255 let prev_pos = -1;
262256 for (const [index, elem] of entry.entries()) {
263 const entry_pos = lookForEntry(elem, results[key]);
257 const entry_pos = lookForEntry(elem, resultsTable[key]);
264258 if (entry_pos === -1) {
265259 error_text.push(queryName + "==> Result not found in '" + key + "': '" +
266260 JSON.stringify(elem) + "'");
267261 // By default, we just compare the two first items.
268262 let item_to_diff = 0;
269 if ((!ignore_order || exact_check) && index < results[key].length) {
263 if ((!ignore_order || exact_check) && index < resultsTable[key].length) {
270264 item_to_diff = index;
271265 }
272266 error_text.push("Diff of first error:\n" +
273 betterLookingDiff(elem, results[key][item_to_diff]));
267 betterLookingDiff(elem, resultsTable[key][item_to_diff]));
274268 } else if (exact_check === true && prev_pos + 1 !== entry_pos) {
275269 error_text.push(queryName + "==> Exact check failed at position " + (prev_pos + 1) +
276270 ": expected '" + JSON.stringify(elem) + "' but found '" +
277 JSON.stringify(results[key][index]) + "'");
271 JSON.stringify(resultsTable[key][index]) + "'");
278272 } else if (ignore_order === false && entry_pos < prev_pos) {
279 error_text.push(queryName + "==> '" + JSON.stringify(elem) + "' was supposed " +
280 "to be before '" + JSON.stringify(results[key][prev_pos]) + "'");
273 error_text.push(queryName + "==> '" +
274 JSON.stringify(elem) + "' was supposed to be before '" +
275 JSON.stringify(resultsTable[key][prev_pos]) + "'");
281276 } else {
282277 prev_pos = entry_pos;
283278 }
......@@ -286,19 +281,20 @@ async function runSearch(query, expected, doSearch, loadedFile, queryName) {
286281 return error_text;
287282}
288283
289async function runCorrections(query, corrections, getCorrections, loadedFile) {
290 const qc = await getCorrections(query, loadedFile.FILTER_CRATE);
284async function runCorrections(query, corrections, doSearch, loadedFile) {
285 const { parsedQuery } = await doSearch(query, loadedFile.FILTER_CRATE);
286 const qc = parsedQuery.correction;
291287 const error_text = [];
292288
293289 if (corrections === null) {
294290 if (qc !== null) {
295 error_text.push(`==> expected = null, found = ${qc}`);
291 error_text.push(`==> [correction] expected = null, found = ${qc}`);
296292 }
297293 return error_text;
298294 }
299295
300 if (qc !== corrections.toLowerCase()) {
301 error_text.push(`==> expected = ${corrections}, found = ${qc}`);
296 if (qc.toLowerCase() !== corrections.toLowerCase()) {
297 error_text.push(`==> [correction] expected = ${corrections}, found = ${qc}`);
302298 }
303299
304300 return error_text;
......@@ -320,7 +316,7 @@ function checkResult(error_text, loadedFile, displaySuccess) {
320316 return 1;
321317}
322318
323async function runCheckInner(callback, loadedFile, entry, getCorrections, extra) {
319async function runCheckInner(callback, loadedFile, entry, extra, doSearch) {
324320 if (typeof entry.query !== "string") {
325321 console.log("FAILED");
326322 console.log("==> Missing `query` field");
......@@ -338,7 +334,7 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
338334 error_text = await runCorrections(
339335 entry.query,
340336 entry.correction,
341 getCorrections,
337 doSearch,
342338 loadedFile,
343339 );
344340 if (checkResult(error_text, loadedFile, false) !== 0) {
......@@ -348,16 +344,16 @@ async function runCheckInner(callback, loadedFile, entry, getCorrections, extra)
348344 return true;
349345}
350346
351async function runCheck(loadedFile, key, getCorrections, callback) {
347async function runCheck(loadedFile, key, doSearch, callback) {
352348 const expected = loadedFile[key];
353349
354350 if (Array.isArray(expected)) {
355351 for (const entry of expected) {
356 if (!await runCheckInner(callback, loadedFile, entry, getCorrections, true)) {
352 if (!await runCheckInner(callback, loadedFile, entry, true, doSearch)) {
357353 return 1;
358354 }
359355 }
360 } else if (!await runCheckInner(callback, loadedFile, expected, getCorrections, false)) {
356 } else if (!await runCheckInner(callback, loadedFile, expected, false, doSearch)) {
361357 return 1;
362358 }
363359 console.log("OK");
......@@ -368,7 +364,7 @@ function hasCheck(content, checkName) {
368364 return content.startsWith(`const ${checkName}`) || content.includes(`\nconst ${checkName}`);
369365}
370366
371async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
367async function runChecks(testFile, doSearch, parseQuery) {
372368 let checkExpected = false;
373369 let checkParsed = false;
374370 let testFileContent = readFile(testFile);
......@@ -397,12 +393,12 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
397393 let res = 0;
398394
399395 if (checkExpected) {
400 res += await runCheck(loadedFile, "EXPECTED", getCorrections, (query, expected, text) => {
396 res += await runCheck(loadedFile, "EXPECTED", doSearch, (query, expected, text) => {
401397 return runSearch(query, expected, doSearch, loadedFile, text);
402398 });
403399 }
404400 if (checkParsed) {
405 res += await runCheck(loadedFile, "PARSED", getCorrections, (query, expected, text) => {
401 res += await runCheck(loadedFile, "PARSED", doSearch, (query, expected, text) => {
406402 return runParser(query, expected, parseQuery, text);
407403 });
408404 }
......@@ -416,71 +412,89 @@ async function runChecks(testFile, doSearch, parseQuery, getCorrections) {
416412 * @param {string} resource_suffix - Version number between filename and .js, e.g. "1.59.0"
417413 * @returns {Object} - Object containing keys: `doSearch`, which runs a search
418414 * with the loaded index and returns a table of results; `parseQuery`, which is the
419 * `parseQuery` function exported from the search module; and `getCorrections`, which runs
415 * `parseQuery` function exported from the search module, which runs
420416 * a search but returns type name corrections instead of results.
421417 */
422function loadSearchJS(doc_folder, resource_suffix) {
423 const searchIndexJs = path.join(doc_folder, "search-index" + resource_suffix + ".js");
424 const searchIndex = require(searchIndexJs);
425
426 globalThis.searchState = {
427 descShards: new Map(),
428 loadDesc: async function({descShard, descIndex}) {
429 if (descShard.promise === null) {
430 descShard.promise = new Promise((resolve, reject) => {
431 descShard.resolve = resolve;
432 const ds = descShard;
433 const fname = `${ds.crate}-desc-${ds.shard}-${resource_suffix}.js`;
434 fs.readFile(
435 `${doc_folder}/search.desc/${descShard.crate}/${fname}`,
436 (err, data) => {
437 if (err) {
438 reject(err);
439 } else {
440 eval(data.toString("utf8"));
441 }
442 },
443 );
444 });
445 }
446 const list = await descShard.promise;
447 return list[descIndex];
448 },
449 loadedDescShard: function(crate, shard, data) {
450 this.descShards.get(crate)[shard].resolve(data.split("\n"));
451 },
452 };
453
418async function loadSearchJS(doc_folder, resource_suffix) {
454419 const staticFiles = path.join(doc_folder, "static.files");
420 const stringdexJs = fs.readdirSync(staticFiles).find(f => f.match(/stringdex.*\.js$/));
421 const stringdexModule = require(path.join(staticFiles, stringdexJs));
455422 const searchJs = fs.readdirSync(staticFiles).find(f => f.match(/search.*\.js$/));
456423 const searchModule = require(path.join(staticFiles, searchJs));
457 searchModule.initSearch(searchIndex.searchIndex);
458 const docSearch = searchModule.docSearch;
424 globalThis.nonnull = (x, msg) => {
425 if (x === null) {
426 throw (msg || "unexpected null value!");
427 } else {
428 return x;
429 }
430 };
431 const { docSearch, DocSearch } = await searchModule.initSearch(
432 stringdexModule.Stringdex,
433 stringdexModule.RoaringBitmap,
434 {
435 loadRoot: callbacks => {
436 for (const key in callbacks) {
437 if (Object.hasOwn(callbacks, key)) {
438 globalThis[key] = callbacks[key];
439 }
440 }
441 const rootJs = readFile(path.join(doc_folder, "search.index/root" +
442 resource_suffix + ".js"));
443 eval(rootJs);
444 },
445 loadTreeByHash: hashHex => {
446 const shardJs = readFile(path.join(doc_folder, "search.index/" + hashHex + ".js"));
447 eval(shardJs);
448 },
449 loadDataByNameAndHash: (name, hashHex) => {
450 const shardJs = readFile(path.join(doc_folder, "search.index/" + name + "/" +
451 hashHex + ".js"));
452 eval(shardJs);
453 },
454 },
455 );
459456 return {
460457 doSearch: async function(queryStr, filterCrate, currentCrate) {
461 const result = await docSearch.execQuery(searchModule.parseQuery(queryStr),
462 filterCrate, currentCrate);
458 const parsedQuery = DocSearch.parseQuery(queryStr);
459 const result = await docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
460 const resultsTable = {};
463461 for (const tab in result) {
464462 if (!Object.prototype.hasOwnProperty.call(result, tab)) {
465463 continue;
466464 }
467 if (!(result[tab] instanceof Array)) {
465 if (!isGeneratorObject(result[tab])) {
468466 continue;
469467 }
470 for (const entry of result[tab]) {
468 resultsTable[tab] = [];
469 for await (const entry of result[tab]) {
470 const flatEntry = Object.assign({
471 crate: entry.item.crate,
472 name: entry.item.name,
473 path: entry.item.modulePath,
474 exactPath: entry.item.exactModulePath,
475 ty: entry.item.ty,
476 }, entry);
471477 for (const key in entry) {
472478 if (!Object.prototype.hasOwnProperty.call(entry, key)) {
473479 continue;
474480 }
475 if (key === "displayTypeSignature" && entry.displayTypeSignature !== null) {
476 const {type, mappedNames, whereClause} =
477 await entry.displayTypeSignature;
478 entry.displayType = arrayToCode(type);
479 entry.displayMappedNames = [...mappedNames.entries()]
481 if (key === "desc" && entry.desc !== null) {
482 flatEntry.desc = await entry.desc;
483 } else if (key === "displayTypeSignature" &&
484 entry.displayTypeSignature !== null
485 ) {
486 flatEntry.displayTypeSignature = await entry.displayTypeSignature;
487 const {
488 type,
489 mappedNames,
490 whereClause,
491 } = flatEntry.displayTypeSignature;
492 flatEntry.displayType = arrayToCode(type);
493 flatEntry.displayMappedNames = [...mappedNames.entries()]
480494 .map(([name, qname]) => {
481495 return `${name} = ${qname}`;
482496 }).join(", ");
483 entry.displayWhereClause = [...whereClause.entries()]
497 flatEntry.displayWhereClause = [...whereClause.entries()]
484498 .flatMap(([name, value]) => {
485499 if (value.length === 0) {
486500 return [];
......@@ -489,16 +503,12 @@ function loadSearchJS(doc_folder, resource_suffix) {
489503 }).join(", ");
490504 }
491505 }
506 resultsTable[tab].push(flatEntry);
492507 }
493508 }
494 return result;
509 return { resultsTable, parsedQuery };
495510 },
496 getCorrections: function(queryStr, filterCrate, currentCrate) {
497 const parsedQuery = searchModule.parseQuery(queryStr);
498 docSearch.execQuery(parsedQuery, filterCrate, currentCrate);
499 return parsedQuery.correction;
500 },
501 parseQuery: searchModule.parseQuery,
511 parseQuery: DocSearch.parseQuery,
502512 };
503513}
504514
......@@ -570,7 +580,7 @@ async function main(argv) {
570580 return 1;
571581 }
572582
573 const parseAndSearch = loadSearchJS(
583 const parseAndSearch = await loadSearchJS(
574584 opts["doc_folder"],
575585 opts["resource_suffix"],
576586 );
......@@ -579,14 +589,11 @@ async function main(argv) {
579589 const doSearch = function(queryStr, filterCrate) {
580590 return parseAndSearch.doSearch(queryStr, filterCrate, opts["crate_name"]);
581591 };
582 const getCorrections = function(queryStr, filterCrate) {
583 return parseAndSearch.getCorrections(queryStr, filterCrate, opts["crate_name"]);
584 };
585592
586593 if (opts["test_file"].length !== 0) {
587594 for (const file of opts["test_file"]) {
588595 process.stdout.write(`Testing ${file} ... `);
589 errors += await runChecks(file, doSearch, parseAndSearch.parseQuery, getCorrections);
596 errors += await runChecks(file, doSearch, parseAndSearch.parseQuery);
590597 }
591598 } else if (opts["test_folder"].length !== 0) {
592599 for (const file of fs.readdirSync(opts["test_folder"])) {
......@@ -595,7 +602,7 @@ async function main(argv) {
595602 }
596603 process.stdout.write(`Testing ${file} ... `);
597604 errors += await runChecks(path.join(opts["test_folder"], file), doSearch,
598 parseAndSearch.parseQuery, getCorrections);
605 parseAndSearch.parseQuery);
599606 }
600607 }
601608 return errors > 0 ? 1 : 0;
tests/codegen-llvm/enum/enum-aggregate.rs+4-4
......@@ -27,7 +27,7 @@ fn make_none_bool() -> Option<bool> {
2727
2828#[no_mangle]
2929fn make_some_ordering(x: Ordering) -> Option<Ordering> {
30 // CHECK-LABEL: i8 @make_some_ordering(i8 %x)
30 // CHECK-LABEL: i8 @make_some_ordering(i8{{( signext)?}} %x)
3131 // CHECK-NEXT: start:
3232 // CHECK-NEXT: ret i8 %x
3333 Some(x)
......@@ -35,7 +35,7 @@ fn make_some_ordering(x: Ordering) -> Option<Ordering> {
3535
3636#[no_mangle]
3737fn make_some_u16(x: u16) -> Option<u16> {
38 // CHECK-LABEL: { i16, i16 } @make_some_u16(i16 %x)
38 // CHECK-LABEL: { i16, i16 } @make_some_u16(i16{{( zeroext)?}} %x)
3939 // CHECK-NEXT: start:
4040 // CHECK-NEXT: %0 = insertvalue { i16, i16 } { i16 1, i16 poison }, i16 %x, 1
4141 // CHECK-NEXT: ret { i16, i16 } %0
......@@ -52,7 +52,7 @@ fn make_none_u16() -> Option<u16> {
5252
5353#[no_mangle]
5454fn make_some_nzu32(x: NonZero<u32>) -> Option<NonZero<u32>> {
55 // CHECK-LABEL: i32 @make_some_nzu32(i32 %x)
55 // CHECK-LABEL: i32 @make_some_nzu32(i32{{( signext)?}} %x)
5656 // CHECK-NEXT: start:
5757 // CHECK-NEXT: ret i32 %x
5858 Some(x)
......@@ -114,7 +114,7 @@ fn make_uninhabited_err_indirectly(n: Never) -> Result<u32, Never> {
114114fn make_fully_uninhabited_result(v: u32, n: Never) -> Result<(u32, Never), (Never, u32)> {
115115 // Actually reaching this would be UB, so we don't actually build a result.
116116
117 // CHECK-LABEL: { i32, i32 } @make_fully_uninhabited_result(i32 %v)
117 // CHECK-LABEL: { i32, i32 } @make_fully_uninhabited_result(i32{{( signext)?}} %v)
118118 // CHECK-NEXT: start:
119119 // CHECK-NEXT: call void @llvm.trap()
120120 // CHECK-NEXT: call void @llvm.trap()
tests/codegen-llvm/enum/enum-match.rs+1-1
......@@ -739,7 +739,7 @@ pub enum Tricky {
739739
740740const _: () = assert!(std::intrinsics::discriminant_value(&Tricky::V100) == 100);
741741
742// CHECK-LABEL: define noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @discriminant6(i8 noundef %e)
742// CHECK-LABEL: define noundef{{( range\(i8 [0-9]+, [0-9]+\))?}} i8 @discriminant6(i8 noundef{{( zeroext)?}} %e)
743743// CHECK-NEXT: start:
744744// CHECK-NEXT: %[[REL_VAR:.+]] = add i8 %e, -66
745745// CHECK-NEXT: %[[IS_NICHE:.+]] = icmp ult i8 %[[REL_VAR]], -56
tests/codegen-llvm/enum/enum-transparent-extract.rs+1-1
......@@ -9,7 +9,7 @@ pub enum Never {}
99
1010#[no_mangle]
1111pub fn make_unmake_result_never(x: i32) -> i32 {
12 // CHECK-LABEL: define i32 @make_unmake_result_never(i32 %x)
12 // CHECK-LABEL: define i32 @make_unmake_result_never(i32{{( signext)?}} %x)
1313 // CHECK: start:
1414 // CHECK-NEXT: ret i32 %x
1515
tests/codegen-llvm/repeat-operand-zero-len.rs+2-2
......@@ -11,7 +11,7 @@
1111#[repr(transparent)]
1212pub struct Wrapper<T, const N: usize>([T; N]);
1313
14// CHECK-LABEL: define {{.+}}do_repeat{{.+}}(i32 noundef %x)
14// CHECK-LABEL: define {{.+}}do_repeat{{.+}}(i32 noundef{{( signext)?}} %x)
1515// CHECK-NEXT: start:
1616// CHECK-NOT: alloca
1717// CHECK-NEXT: ret void
......@@ -23,6 +23,6 @@ pub fn do_repeat<T: Copy, const N: usize>(x: T) -> Wrapper<T, N> {
2323// CHECK-LABEL: @trigger_repeat_zero_len
2424#[no_mangle]
2525pub fn trigger_repeat_zero_len() -> Wrapper<u32, 0> {
26 // CHECK: call void {{.+}}do_repeat{{.+}}(i32 noundef 4)
26 // CHECK: call void {{.+}}do_repeat{{.+}}(i32 noundef{{( signext)?}} 4)
2727 do_repeat(4)
2828}
tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-no-sanitize.rs deleted-18
......@@ -1,18 +0,0 @@
1// Verifies that pointer type membership tests for indirect calls are omitted.
2//
3//@ needs-sanitizer-cfi
4//@ compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0
5
6#![crate_type = "lib"]
7#![feature(no_sanitize)]
8
9#[no_sanitize(cfi)]
10pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
11 // CHECK-LABEL: emit_type_checks_attr_no_sanitize::foo
12 // CHECK: Function Attrs: {{.*}}
13 // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}}
14 // CHECK: start:
15 // CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg)
16 // CHECK-NEXT: ret i32 {{%.+}}
17 f(arg)
18}
tests/codegen-llvm/sanitizer/cfi/emit-type-checks-attr-sanitize-off.rs created+18
......@@ -0,0 +1,18 @@
1// Verifies that pointer type membership tests for indirect calls are omitted.
2//
3//@ needs-sanitizer-cfi
4//@ compile-flags: -Clto -Cno-prepopulate-passes -Ctarget-feature=-crt-static -Zsanitizer=cfi -Copt-level=0
5
6#![crate_type = "lib"]
7#![feature(sanitize)]
8
9#[sanitize(cfi = "off")]
10pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
11 // CHECK-LABEL: emit_type_checks_attr_sanitize_off::foo
12 // CHECK: Function Attrs: {{.*}}
13 // CHECK-LABEL: define{{.*}}foo{{.*}}!type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}} !type !{{[0-9]+}}
14 // CHECK: start:
15 // CHECK-NEXT: {{%.+}} = call i32 %f(i32{{.*}} %arg)
16 // CHECK-NEXT: ret i32 {{%.+}}
17 f(arg)
18}
tests/codegen-llvm/sanitizer/kasan-emits-instrumentation.rs+2-2
......@@ -13,7 +13,7 @@
1313//@[x86_64] needs-llvm-components: x86
1414
1515#![crate_type = "rlib"]
16#![feature(no_core, no_sanitize, lang_items)]
16#![feature(no_core, sanitize, lang_items)]
1717#![no_core]
1818
1919extern crate minicore;
......@@ -25,7 +25,7 @@ use minicore::*;
2525// CHECK: start:
2626// CHECK-NOT: call void @__asan_report_load
2727// CHECK: }
28#[no_sanitize(address)]
28#[sanitize(address = "off")]
2929pub fn unsanitized(b: &mut u8) -> u8 {
3030 *b
3131}
tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-no-sanitize.rs deleted-27
......@@ -1,27 +0,0 @@
1// Verifies that KCFI operand bundles are omitted.
2//
3//@ add-core-stubs
4//@ revisions: aarch64 x86_64
5//@ [aarch64] compile-flags: --target aarch64-unknown-none
6//@ [aarch64] needs-llvm-components: aarch64
7//@ [x86_64] compile-flags: --target x86_64-unknown-none
8//@ [x86_64] needs-llvm-components: x86
9//@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
10
11#![crate_type = "lib"]
12#![feature(no_core, no_sanitize, lang_items)]
13#![no_core]
14
15extern crate minicore;
16use minicore::*;
17
18#[no_sanitize(kcfi)]
19pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
20 // CHECK-LABEL: emit_kcfi_operand_bundle_attr_no_sanitize::foo
21 // CHECK: Function Attrs: {{.*}}
22 // CHECK-LABEL: define{{.*}}foo{{.*}}!{{<unknown kind #36>|kcfi_type}} !{{[0-9]+}}
23 // CHECK: start:
24 // CHECK-NOT: {{%.+}} = call {{(noundef )*}}i32 %f(i32 {{(noundef )*}}%arg){{.*}}[ "kcfi"(i32 {{[-0-9]+}}) ]
25 // CHECK: ret i32 {{%.+}}
26 f(arg)
27}
tests/codegen-llvm/sanitizer/kcfi/emit-kcfi-operand-bundle-attr-sanitize-off.rs created+27
......@@ -0,0 +1,27 @@
1// Verifies that KCFI operand bundles are omitted.
2//
3//@ add-core-stubs
4//@ revisions: aarch64 x86_64
5//@ [aarch64] compile-flags: --target aarch64-unknown-none
6//@ [aarch64] needs-llvm-components: aarch64
7//@ [x86_64] compile-flags: --target x86_64-unknown-none
8//@ [x86_64] needs-llvm-components: x86
9//@ compile-flags: -Cno-prepopulate-passes -Zsanitizer=kcfi -Copt-level=0
10
11#![crate_type = "lib"]
12#![feature(no_core, sanitize, lang_items)]
13#![no_core]
14
15extern crate minicore;
16use minicore::*;
17
18#[sanitize(kcfi = "off")]
19pub fn foo(f: fn(i32) -> i32, arg: i32) -> i32 {
20 // CHECK-LABEL: emit_kcfi_operand_bundle_attr_sanitize_off::foo
21 // CHECK: Function Attrs: {{.*}}
22 // CHECK-LABEL: define{{.*}}foo{{.*}}!{{<unknown kind #36>|kcfi_type}} !{{[0-9]+}}
23 // CHECK: start:
24 // CHECK-NOT: {{%.+}} = call {{(noundef )*}}i32 %f(i32 {{(noundef )*}}%arg){{.*}}[ "kcfi"(i32 {{[-0-9]+}}) ]
25 // CHECK: ret i32 {{%.+}}
26 f(arg)
27}
tests/codegen-llvm/sanitizer/no-sanitize-inlining.rs deleted-31
......@@ -1,31 +0,0 @@
1// Verifies that no_sanitize attribute prevents inlining when
2// given sanitizer is enabled, but has no effect on inlining otherwise.
3//
4//@ needs-sanitizer-address
5//@ needs-sanitizer-leak
6//@ revisions: ASAN LSAN
7//@ compile-flags: -Copt-level=3 -Zmir-opt-level=4 -Ctarget-feature=-crt-static
8//@[ASAN] compile-flags: -Zsanitizer=address
9//@[LSAN] compile-flags: -Zsanitizer=leak
10
11#![crate_type = "lib"]
12#![feature(no_sanitize)]
13
14// ASAN-LABEL: define void @test
15// ASAN: call {{.*}} @random_inline
16// ASAN: }
17//
18// LSAN-LABEL: define void @test
19// LSAN-NOT: call
20// LSAN: }
21#[no_mangle]
22pub fn test(n: &mut u32) {
23 random_inline(n);
24}
25
26#[no_sanitize(address)]
27#[inline]
28#[no_mangle]
29pub fn random_inline(n: &mut u32) {
30 *n = 42;
31}
tests/codegen-llvm/sanitizer/no-sanitize.rs deleted-39
......@@ -1,39 +0,0 @@
1// Verifies that no_sanitize attribute can be used to
2// selectively disable sanitizer instrumentation.
3//
4//@ needs-sanitizer-address
5//@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static -Copt-level=0
6
7#![crate_type = "lib"]
8#![feature(no_sanitize)]
9
10// CHECK: @UNSANITIZED = constant{{.*}} no_sanitize_address
11// CHECK-NOT: @__asan_global_UNSANITIZED
12#[no_mangle]
13#[no_sanitize(address)]
14pub static UNSANITIZED: u32 = 0;
15
16// CHECK: @__asan_global_SANITIZED
17#[no_mangle]
18pub static SANITIZED: u32 = 0;
19
20// CHECK-LABEL: ; no_sanitize::unsanitized
21// CHECK-NEXT: ; Function Attrs:
22// CHECK-NOT: sanitize_address
23// CHECK: start:
24// CHECK-NOT: call void @__asan_report_load
25// CHECK: }
26#[no_sanitize(address)]
27pub fn unsanitized(b: &mut u8) -> u8 {
28 *b
29}
30
31// CHECK-LABEL: ; no_sanitize::sanitized
32// CHECK-NEXT: ; Function Attrs:
33// CHECK: sanitize_address
34// CHECK: start:
35// CHECK: call void @__asan_report_load
36// CHECK: }
37pub fn sanitized(b: &mut u8) -> u8 {
38 *b
39}
tests/codegen-llvm/sanitizer/sanitize-off-asan-kasan.rs created+42
......@@ -0,0 +1,42 @@
1// Verifies that the `#[sanitize(address = "off")]` attribute also turns off
2// the kernel address sanitizer.
3//
4//@ add-core-stubs
5//@ compile-flags: -Zsanitizer=kernel-address -Ctarget-feature=-crt-static -Copt-level=0
6//@ revisions: aarch64 riscv64imac riscv64gc x86_64
7//@[aarch64] compile-flags: --target aarch64-unknown-none
8//@[aarch64] needs-llvm-components: aarch64
9//@[riscv64imac] compile-flags: --target riscv64imac-unknown-none-elf
10//@[riscv64imac] needs-llvm-components: riscv
11//@[riscv64gc] compile-flags: --target riscv64gc-unknown-none-elf
12//@[riscv64gc] needs-llvm-components: riscv
13//@[x86_64] compile-flags: --target x86_64-unknown-none
14//@[x86_64] needs-llvm-components: x86
15
16#![crate_type = "rlib"]
17#![feature(no_core, sanitize, lang_items)]
18#![no_core]
19
20extern crate minicore;
21use minicore::*;
22
23// CHECK-LABEL: ; sanitize_off_asan_kasan::unsanitized
24// CHECK-NEXT: ; Function Attrs:
25// CHECK-NOT: sanitize_address
26// CHECK: start:
27// CHECK-NOT: call void @__asan_report_load
28// CHECK: }
29#[sanitize(address = "off")]
30pub fn unsanitized(b: &mut u8) -> u8 {
31 *b
32}
33
34// CHECK-LABEL: ; sanitize_off_asan_kasan::sanitized
35// CHECK-NEXT: ; Function Attrs:
36// CHECK: sanitize_address
37// CHECK: start:
38// CHECK: call void @__asan_report_load
39// CHECK: }
40pub fn sanitized(b: &mut u8) -> u8 {
41 *b
42}
tests/codegen-llvm/sanitizer/sanitize-off-inlining.rs created+31
......@@ -0,0 +1,31 @@
1// Verifies that sanitize(xyz = "off") attribute prevents inlining when
2// given sanitizer is enabled, but has no effect on inlining otherwise.
3//
4//@ needs-sanitizer-address
5//@ needs-sanitizer-leak
6//@ revisions: ASAN LSAN
7//@ compile-flags: -Copt-level=3 -Zmir-opt-level=4 -Ctarget-feature=-crt-static
8//@[ASAN] compile-flags: -Zsanitizer=address
9//@[LSAN] compile-flags: -Zsanitizer=leak
10
11#![crate_type = "lib"]
12#![feature(sanitize)]
13
14// ASAN-LABEL: define void @test
15// ASAN: call {{.*}} @random_inline
16// ASAN: }
17//
18// LSAN-LABEL: define void @test
19// LSAN-NOT: call
20// LSAN: }
21#[no_mangle]
22pub fn test(n: &mut u32) {
23 random_inline(n);
24}
25
26#[sanitize(address = "off")]
27#[inline]
28#[no_mangle]
29pub fn random_inline(n: &mut u32) {
30 *n = 42;
31}
tests/codegen-llvm/sanitizer/sanitize-off-kasan-asan.rs created+29
......@@ -0,0 +1,29 @@
1// Verifies that the `#[sanitize(kernel_address = "off")]` attribute also turns off
2// the address sanitizer.
3//
4//@ needs-sanitizer-address
5//@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static -Copt-level=0
6
7#![crate_type = "lib"]
8#![feature(sanitize)]
9
10// CHECK-LABEL: ; sanitize_off_kasan_asan::unsanitized
11// CHECK-NEXT: ; Function Attrs:
12// CHECK-NOT: sanitize_address
13// CHECK: start:
14// CHECK-NOT: call void @__asan_report_load
15// CHECK: }
16#[sanitize(kernel_address = "off")]
17pub fn unsanitized(b: &mut u8) -> u8 {
18 *b
19}
20
21// CHECK-LABEL: ; sanitize_off_kasan_asan::sanitized
22// CHECK-NEXT: ; Function Attrs:
23// CHECK: sanitize_address
24// CHECK: start:
25// CHECK: call void @__asan_report_load
26// CHECK: }
27pub fn sanitized(b: &mut u8) -> u8 {
28 *b
29}
tests/codegen-llvm/sanitizer/sanitize-off.rs created+138
......@@ -0,0 +1,138 @@
1// Verifies that the `#[sanitize(address = "off")]` attribute can be used to
2// selectively disable sanitizer instrumentation.
3//
4//@ needs-sanitizer-address
5//@ compile-flags: -Zsanitizer=address -Ctarget-feature=-crt-static -Copt-level=0
6
7#![crate_type = "lib"]
8#![feature(sanitize)]
9
10// CHECK: @UNSANITIZED = constant{{.*}} no_sanitize_address
11// CHECK-NOT: @__asan_global_SANITIZED
12#[no_mangle]
13#[sanitize(address = "off")]
14pub static UNSANITIZED: u32 = 0;
15
16// CHECK: @__asan_global_SANITIZED
17#[no_mangle]
18pub static SANITIZED: u32 = 0;
19
20// CHECK-LABEL: ; sanitize_off::unsanitized
21// CHECK-NEXT: ; Function Attrs:
22// CHECK-NOT: sanitize_address
23// CHECK: start:
24// CHECK-NOT: call void @__asan_report_load
25// CHECK: }
26#[sanitize(address = "off")]
27pub fn unsanitized(b: &mut u8) -> u8 {
28 *b
29}
30
31// CHECK-LABEL: ; sanitize_off::sanitized
32// CHECK-NEXT: ; Function Attrs:
33// CHECK: sanitize_address
34// CHECK: start:
35// CHECK: call void @__asan_report_load
36// CHECK: }
37pub fn sanitized(b: &mut u8) -> u8 {
38 *b
39}
40
41#[sanitize(address = "off")]
42pub mod foo {
43 // CHECK-LABEL: ; sanitize_off::foo::unsanitized
44 // CHECK-NEXT: ; Function Attrs:
45 // CHECK-NOT: sanitize_address
46 // CHECK: start:
47 // CHECK-NOT: call void @__asan_report_load
48 // CHECK: }
49 pub fn unsanitized(b: &mut u8) -> u8 {
50 *b
51 }
52
53 // CHECK-LABEL: ; sanitize_off::foo::sanitized
54 // CHECK-NEXT: ; Function Attrs:
55 // CHECK: sanitize_address
56 // CHECK: start:
57 // CHECK: call void @__asan_report_load
58 // CHECK: }
59 #[sanitize(address = "on")]
60 pub fn sanitized(b: &mut u8) -> u8 {
61 *b
62 }
63}
64
65pub trait MyTrait {
66 fn unsanitized(&self, b: &mut u8) -> u8;
67 fn sanitized(&self, b: &mut u8) -> u8;
68
69 // CHECK-LABEL: ; sanitize_off::MyTrait::unsanitized_default
70 // CHECK-NEXT: ; Function Attrs:
71 // CHECK-NOT: sanitize_address
72 // CHECK: start:
73 // CHECK-NOT: call void @__asan_report_load
74 // CHECK: }
75 #[sanitize(address = "off")]
76 fn unsanitized_default(&self, b: &mut u8) -> u8 {
77 *b
78 }
79
80 // CHECK-LABEL: ; sanitize_off::MyTrait::sanitized_default
81 // CHECK-NEXT: ; Function Attrs:
82 // CHECK: sanitize_address
83 // CHECK: start:
84 // CHECK: call void @__asan_report_load
85 // CHECK: }
86 fn sanitized_default(&self, b: &mut u8) -> u8 {
87 *b
88 }
89}
90
91#[sanitize(address = "off")]
92impl MyTrait for () {
93 // CHECK-LABEL: ; <() as sanitize_off::MyTrait>::unsanitized
94 // CHECK-NEXT: ; Function Attrs:
95 // CHECK-NOT: sanitize_address
96 // CHECK: start:
97 // CHECK-NOT: call void @__asan_report_load
98 // CHECK: }
99 fn unsanitized(&self, b: &mut u8) -> u8 {
100 *b
101 }
102
103 // CHECK-LABEL: ; <() as sanitize_off::MyTrait>::sanitized
104 // CHECK-NEXT: ; Function Attrs:
105 // CHECK: sanitize_address
106 // CHECK: start:
107 // CHECK: call void @__asan_report_load
108 // CHECK: }
109 #[sanitize(address = "on")]
110 fn sanitized(&self, b: &mut u8) -> u8 {
111 *b
112 }
113}
114
115pub fn expose_trait(b: &mut u8) -> u8 {
116 <() as MyTrait>::unsanitized_default(&(), b);
117 <() as MyTrait>::sanitized_default(&(), b)
118}
119
120#[sanitize(address = "off")]
121pub mod outer {
122 #[sanitize(thread = "off")]
123 pub mod inner {
124 // CHECK-LABEL: ; sanitize_off::outer::inner::unsanitized
125 // CHECK-NEXT: ; Function Attrs:
126 // CHECK-NOT: sanitize_address
127 // CHECK: start:
128 // CHECK-NOT: call void @__asan_report_load
129 // CHECK: }
130 pub fn unsanitized() {
131 let xs = [0, 1, 2, 3];
132 // Avoid optimizing everything out.
133 let xs = std::hint::black_box(xs.as_ptr());
134 let code = unsafe { *xs.offset(4) };
135 std::process::exit(code);
136 }
137 }
138}
tests/codegen-llvm/sanitizer/scs-attr-check.rs+2-2
......@@ -5,7 +5,7 @@
55//@ compile-flags: -Zsanitizer=shadow-call-stack
66
77#![crate_type = "lib"]
8#![feature(no_sanitize)]
8#![feature(sanitize)]
99
1010// CHECK: ; sanitizer_scs_attr_check::scs
1111// CHECK-NEXT: ; Function Attrs:{{.*}}shadowcallstack
......@@ -13,5 +13,5 @@ pub fn scs() {}
1313
1414// CHECK: ; sanitizer_scs_attr_check::no_scs
1515// CHECK-NOT: ; Function Attrs:{{.*}}shadowcallstack
16#[no_sanitize(shadow_call_stack)]
16#[sanitize(shadow_call_stack = "off")]
1717pub fn no_scs() {}
tests/codegen-llvm/transmute-scalar.rs+3-10
......@@ -1,8 +1,9 @@
11//@ add-core-stubs
2//@ compile-flags: -C opt-level=0 -C no-prepopulate-passes
2//@ compile-flags: -C opt-level=0 -C no-prepopulate-passes --target=x86_64-unknown-linux-gnu
3//@ needs-llvm-components: x86
34
45#![crate_type = "lib"]
5#![feature(no_core, repr_simd, arm_target_feature, mips_target_feature, s390x_target_feature)]
6#![feature(no_core, repr_simd)]
67#![no_core]
78extern crate minicore;
89
......@@ -117,11 +118,7 @@ struct S([i64; 1]);
117118// CHECK-NEXT: %[[TEMP:.+]] = load i64, ptr %[[RET]]
118119// CHECK-NEXT: ret i64 %[[TEMP]]
119120#[no_mangle]
120#[cfg_attr(target_family = "wasm", target_feature(enable = "simd128"))]
121#[cfg_attr(target_arch = "arm", target_feature(enable = "neon"))]
122121#[cfg_attr(target_arch = "x86", target_feature(enable = "sse"))]
123#[cfg_attr(target_arch = "mips", target_feature(enable = "msa"))]
124#[cfg_attr(target_arch = "s390x", target_feature(enable = "vector"))]
125122pub extern "C" fn single_element_simd_to_scalar(b: S) -> i64 {
126123 unsafe { mem::transmute(b) }
127124}
......@@ -133,11 +130,7 @@ pub extern "C" fn single_element_simd_to_scalar(b: S) -> i64 {
133130// CHECK-NEXT: %[[TEMP:.+]] = load <1 x i64>, ptr %[[RET]]
134131// CHECK-NEXT: ret <1 x i64> %[[TEMP]]
135132#[no_mangle]
136#[cfg_attr(target_family = "wasm", target_feature(enable = "simd128"))]
137#[cfg_attr(target_arch = "arm", target_feature(enable = "neon"))]
138133#[cfg_attr(target_arch = "x86", target_feature(enable = "sse"))]
139#[cfg_attr(target_arch = "mips", target_feature(enable = "msa"))]
140#[cfg_attr(target_arch = "s390x", target_feature(enable = "vector"))]
141134pub extern "C" fn scalar_to_single_element_simd(b: i64) -> S {
142135 unsafe { mem::transmute(b) }
143136}
tests/codegen-llvm/uninhabited-transparent-return-abi.rs+1-1
......@@ -36,7 +36,7 @@ pub fn test_uninhabited_ret_by_ref() {
3636pub fn test_uninhabited_ret_by_ref_with_arg(rsi: u32) {
3737 // CHECK: %_2 = alloca [24 x i8], align {{8|4}}
3838 // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 24, )?}}ptr nonnull %_2)
39 // CHECK-NEXT: call void @opaque_with_arg({{.*}} sret([24 x i8]) {{.*}} %_2, i32 noundef %rsi) #2
39 // CHECK-NEXT: call void @opaque_with_arg({{.*}} sret([24 x i8]) {{.*}} %_2, i32 noundef{{( signext)?}} %rsi) #2
4040 // CHECK-NEXT: unreachable
4141 unsafe {
4242 opaque_with_arg(rsi);
tests/mir-opt/inline/inline_compatibility.rs+11-11
......@@ -3,7 +3,7 @@
33//@ compile-flags: -Cpanic=abort
44
55#![crate_type = "lib"]
6#![feature(no_sanitize)]
6#![feature(sanitize)]
77#![feature(c_variadic)]
88
99#[inline]
......@@ -37,22 +37,22 @@ pub unsafe fn f2() {
3737}
3838
3939#[inline]
40#[no_sanitize(address)]
41pub unsafe fn no_sanitize() {}
40#[sanitize(address = "off")]
41pub unsafe fn sanitize_off() {}
4242
43// CHECK-LABEL: fn inlined_no_sanitize()
43// CHECK-LABEL: fn inlined_sanitize_off()
4444// CHECK: bb0: {
4545// CHECK-NEXT: return;
46#[no_sanitize(address)]
47pub unsafe fn inlined_no_sanitize() {
48 no_sanitize();
46#[sanitize(address = "off")]
47pub unsafe fn inlined_sanitize_off() {
48 sanitize_off();
4949}
5050
51// CHECK-LABEL: fn not_inlined_no_sanitize()
51// CHECK-LABEL: fn not_inlined_sanitize_off()
5252// CHECK: bb0: {
53// CHECK-NEXT: no_sanitize()
54pub unsafe fn not_inlined_no_sanitize() {
55 no_sanitize();
53// CHECK-NEXT: sanitize_off()
54pub unsafe fn not_inlined_sanitize_off() {
55 sanitize_off();
5656}
5757
5858// CHECK-LABEL: fn not_inlined_c_variadic()
tests/run-make/emit-shared-files/rmake.rs+1-1
......@@ -19,7 +19,7 @@ fn main() {
1919 .args(&["--extend-css", "z.css"])
2020 .input("x.rs")
2121 .run();
22 assert!(path("invocation-only/search-index-xxx.js").exists());
22 assert!(path("invocation-only/search.index/root-xxx.js").exists());
2323 assert!(path("invocation-only/crates-xxx.js").exists());
2424 assert!(path("invocation-only/settings.html").exists());
2525 assert!(path("invocation-only/x/all.html").exists());
tests/run-make/rustdoc-determinism/rmake.rs+2-2
......@@ -15,7 +15,7 @@ fn main() {
1515 rustdoc().input("foo.rs").out_dir(&bar_first).run();
1616
1717 diff()
18 .expected_file(foo_first.join("search-index.js"))
19 .actual_file(bar_first.join("search-index.js"))
18 .expected_file(foo_first.join("search.index/root.js"))
19 .actual_file(bar_first.join("search.index/root.js"))
2020 .run();
2121}
tests/run-make/rustdoc-scrape-examples-paths/rmake.rs+8-8
......@@ -1,16 +1,16 @@
11//! Test to ensure that the rustdoc `scrape-examples` feature is not panicking.
22//! Regression test for <https://github.com/rust-lang/rust/issues/144752>.
33
4use run_make_support::{cargo, path, rfs};
4use run_make_support::cargo;
5use run_make_support::scoped_run::run_in_tmpdir;
56
67fn main() {
78 // We copy the crate to be documented "outside" to prevent documenting
89 // the whole compiler.
9 let tmp = std::env::temp_dir();
10 let test_crate = tmp.join("foo");
11 rfs::copy_dir_all(path("foo"), &test_crate);
12
13 // The `scrape-examples` feature is also implemented in `cargo` so instead of reproducing
14 // what `cargo` does, better to just let `cargo` do it.
15 cargo().current_dir(&test_crate).args(["doc", "-p", "foo", "-Zrustdoc-scrape-examples"]).run();
10 std::env::set_current_dir("foo").unwrap();
11 run_in_tmpdir(|| {
12 // The `scrape-examples` feature is also implemented in `cargo` so instead of reproducing
13 // what `cargo` does, better to just let `cargo` do it.
14 cargo().args(["doc", "-p", "foo", "-Zrustdoc-scrape-examples"]).run();
15 })
1616}
tests/rustdoc-gui/code-example-buttons.goml+4-4
......@@ -5,25 +5,25 @@ include: "utils.goml"
55// First we check we "hover".
66move-cursor-to: ".example-wrap"
77assert-css: (".example-wrap .copy-button", { "visibility": "visible" })
8move-cursor-to: ".search-input"
8move-cursor-to: "#search-button"
99assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
1010
1111// Now we check the click.
1212assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
1313click: ".example-wrap"
14move-cursor-to: ".search-input"
14move-cursor-to: "#search-button"
1515// It should have a new class and be visible.
1616wait-for-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 1)
1717wait-for-css: (".example-wrap:not(:hover) .button-holder.keep-visible", { "visibility": "visible" })
1818// Clicking again will remove the class.
1919click: ".example-wrap"
20move-cursor-to: ".search-input"
20move-cursor-to: "rustdoc-toolbar #search-button"
2121assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
2222assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
2323
2424// Clicking on the "copy code" button shouldn't make the buttons stick.
2525click: ".example-wrap .copy-button"
26move-cursor-to: ".search-input"
26move-cursor-to: "#search-button"
2727assert-count: (".example-wrap:not(:hover) .button-holder.keep-visible", 0)
2828assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
2929// Since we clicked on the copy button, the clipboard content should have been updated.
tests/rustdoc-gui/copy-code.goml+1-1
......@@ -12,7 +12,7 @@ define-function: (
1212 assert-count: (".example-wrap .copy-button", 1)
1313 // We now ensure it's only displayed when the example is hovered.
1414 assert-css: (".example-wrap .copy-button", { "visibility": "visible" })
15 move-cursor-to: ".search-input"
15 move-cursor-to: "rustdoc-toolbar #search-button"
1616 assert-css: (".example-wrap .copy-button", { "visibility": "hidden" })
1717 // Checking that the copy button has the same size as the "copy path" button.
1818 compare-elements-size: (
tests/rustdoc-gui/cursor.goml+2-5
......@@ -1,4 +1,5 @@
11// This test ensures that several clickable items actually have the pointer cursor.
2include: "utils.goml"
23go-to: "file://" + |DOC_PATH| + "/lib2/struct.Foo.html"
34
45// the `[+]/[-]` button
......@@ -8,11 +9,7 @@ assert-css: ("#toggle-all-docs", {"cursor": "pointer"})
89assert-css: ("#copy-path", {"cursor": "pointer"})
910
1011// the search tabs
11write-into: (".search-input", "Foo")
12// To be SURE that the search will be run.
13press-key: 'Enter'
14// Waiting for the search results to appear...
15wait-for: "#search-tabs"
12call-function: ("perform-search", {"query": "Foo"})
1613assert-css: ("#search-tabs > button", {"cursor": "pointer"})
1714
1815// mobile sidebar toggle button
tests/rustdoc-gui/docblock-code-block-line-number.goml+4-4
......@@ -69,7 +69,7 @@ call-function: ("check-colors", {
6969// and make sure it goes away.
7070
7171// First, open the settings menu.
72click: "#settings-menu"
72click: "rustdoc-toolbar .settings-menu"
7373wait-for: "#settings"
7474assert-css: ("#settings", {"display": "block"})
7575
......@@ -121,7 +121,7 @@ call-function: ("check-padding", {
121121define-function: ("check-line-numbers-existence", [], block {
122122 assert-local-storage: {"rustdoc-line-numbers": "true" }
123123 assert-false: ".example-line-numbers"
124 click: "#settings-menu"
124 click: "rustdoc-toolbar .settings-menu"
125125 wait-for: "#settings"
126126
127127 // Then, click the toggle button.
......@@ -137,7 +137,7 @@ define-function: ("check-line-numbers-existence", [], block {
137137 // Line numbers should still be there.
138138 assert-css: ("[data-nosnippet]", { "display": "block"})
139139 // Closing settings menu.
140 click: "#settings-menu"
140 click: "rustdoc-toolbar .settings-menu"
141141 wait-for-css: ("#settings", {"display": "none"})
142142})
143143
......@@ -168,7 +168,7 @@ assert: ".example-wrap > pre.rust"
168168assert-count: (".example-wrap", 2)
169169assert-count: (".example-wrap.digits-1", 2)
170170
171click: "#settings-menu"
171click: "rustdoc-toolbar .settings-menu"
172172wait-for: "#settings"
173173
174174// Then, click the toggle button.
tests/rustdoc-gui/escape-key.goml+4-7
......@@ -1,13 +1,10 @@
11// This test ensures that the "Escape" shortcut is handled correctly based on the
22// current content displayed.
3include: "utils.goml"
34go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
45// First, we check that the search results are hidden when the Escape key is pressed.
5write-into: (".search-input", "test")
6// To be SURE that the search will be run.
7press-key: 'Enter'
8wait-for: "#search h1" // The search element is empty before the first search
6call-function: ("perform-search", {"query": "test"})
97// Check that the currently displayed element is search.
10wait-for: "#alternative-display #search"
118assert-attribute: ("#main-content", {"class": "content hidden"})
129assert-document-property: ({"URL": "index.html?search=test"}, ENDS_WITH)
1310press-key: "Escape"
......@@ -17,8 +14,8 @@ assert-false: "#alternative-display #search"
1714assert-attribute: ("#main-content", {"class": "content"})
1815assert-document-property: ({"URL": "index.html"}, [ENDS_WITH])
1916
20// Check that focusing the search input brings back the search results
21focus: ".search-input"
17// Check that clicking the search button brings back the search results
18click: "#search-button"
2219wait-for: "#alternative-display #search"
2320assert-attribute: ("#main-content", {"class": "content hidden"})
2421assert-document-property: ({"URL": "index.html?search=test"}, ENDS_WITH)
tests/rustdoc-gui/font-serif-change.goml+2-2
......@@ -8,7 +8,7 @@ assert-css: ("body", {"font-family": |serif_font|})
88assert-css: ("p code", {"font-family": |serif_code_font|})
99
1010// We now switch to the sans serif font
11click: "#settings-menu"
11click: "main .settings-menu"
1212wait-for: "#sans-serif-fonts"
1313click: "#sans-serif-fonts"
1414
......@@ -23,7 +23,7 @@ assert-css: ("body", {"font-family": |font|})
2323assert-css: ("p code", {"font-family": |code_font|})
2424
2525// We switch back to the serif font
26click: "#settings-menu"
26click: "main .settings-menu"
2727wait-for: "#sans-serif-fonts"
2828click: "#sans-serif-fonts"
2929
tests/rustdoc-gui/globals.goml+2-3
......@@ -1,6 +1,7 @@
11// Make sure search stores its data in `window`
22// It needs to use a global to avoid racing on search-index.js and search.js
33// https://github.com/rust-lang/rust/pull/118961
4include: "utils.goml"
45
56// URL query
67go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=sa'%3Bda'%3Bds"
......@@ -9,9 +10,7 @@ assert-window-property-false: {"searchIndex": null}
910
1011// Form input
1112go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
12write-into: (".search-input", "Foo")
13press-key: 'Enter'
14wait-for: "#search-tabs"
13call-function: ("perform-search", {"query": "Foo"})
1514assert-window-property-false: {"searchIndex": null}
1615
1716// source sidebar
tests/rustdoc-gui/help-page.goml+8-10
......@@ -6,12 +6,12 @@ assert-css: ("#help", {"display": "block"})
66assert-css: ("#help dd", {"font-size": "16px"})
77assert-false: "#help-button > a"
88assert-css: ("#help", {"display": "block"})
9compare-elements-property: (".sub", "#help", ["offsetWidth"])
10compare-elements-position: (".sub", "#help", ["x"])
9compare-elements-property: (".main-heading", "#help", ["offsetWidth"])
10compare-elements-position: (".main-heading", "#help", ["x"])
1111set-window-size: (500, 1000) // Try mobile next.
1212assert-css: ("#help", {"display": "block"})
13compare-elements-property: (".sub", "#help", ["offsetWidth"])
14compare-elements-position: (".sub", "#help", ["x"])
13compare-elements-property: (".main-heading", "#help", ["offsetWidth"])
14compare-elements-position: (".main-heading", "#help", ["x"])
1515
1616// Checking the color of the elements of the help menu.
1717show-text: true
......@@ -54,19 +54,17 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a"
5454wait-for: "#search-tabs" // Waiting for the search.js to load.
5555set-window-size: (1000, 1000) // Only supported on desktop.
5656assert-false: "#help"
57click: "#help-button > a"
57click: "rustdoc-toolbar .help-menu > a"
5858assert-css: ("#help", {"display": "block"})
5959assert-css: ("#help dd", {"font-size": "16px"})
60click: "#help-button > a"
61assert-css: ("#help", {"display": "none"})
62compare-elements-property-false: (".sub", "#help", ["offsetWidth"])
63compare-elements-position-false: (".sub", "#help", ["x"])
60click: "rustdoc-toolbar .help-menu > a"
61assert-false: "#help"
6462
6563// This test ensures that the "the rustdoc book" anchor link within the help popover works.
6664go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a"
6765wait-for: "#search-tabs" // Waiting for the search.js to load.
6866set-window-size: (1000, 1000) // Popover only appears when the screen width is >700px.
6967assert-false: "#help"
70click: "#help-button > a"
68click: "rustdoc-toolbar .help-menu > a"
7169click: "//*[@id='help']//a[text()='the rustdoc book']"
7270wait-for-document-property: ({"URL": "https://doc.rust-lang.org/"}, STARTS_WITH)
tests/rustdoc-gui/hide-mobile-topbar.goml+4-5
......@@ -1,20 +1,19 @@
11// Checks sidebar resizing stays synced with the setting
2go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
2go-to: "file://" + |DOC_PATH| + "/settings.html"
33set-window-size: (400, 600)
44
55// Verify that the "hide" option is unchecked
6click: "#settings-menu"
76wait-for: "#settings"
87assert-css: ("#settings", {"display": "block"})
98assert-property: ("#hide-sidebar", {"checked": "false"})
10assert-css: (".mobile-topbar", {"display": "flex"})
9assert-css: ("rustdoc-topbar", {"display": "flex"})
1110
1211// Toggle it
1312click: "#hide-sidebar"
1413assert-property: ("#hide-sidebar", {"checked": "true"})
15assert-css: (".mobile-topbar", {"display": "none"})
14assert-css: ("rustdoc-topbar", {"display": "none"})
1615
1716// Toggle it again
1817click: "#hide-sidebar"
1918assert-property: ("#hide-sidebar", {"checked": "false"})
20assert-css: (".mobile-topbar", {"display": "flex"})
19assert-css: ("rustdoc-topbar", {"display": "flex"})
tests/rustdoc-gui/huge-logo.goml-5
......@@ -8,8 +8,3 @@ assert-property: (".sidebar-crate .logo-container", {"offsetWidth": "96", "offse
88// offsetWidth = width of sidebar, offsetHeight = height + top padding
99assert-property: (".sidebar-crate .logo-container img", {"offsetWidth": "48", "offsetHeight": 64})
1010assert-css: (".sidebar-crate .logo-container img", {"border-top-width": "16px", "margin-top": "-16px"})
11
12set-window-size: (400, 600)
13// offset = size + margin
14assert-property: (".mobile-topbar .logo-container", {"offsetWidth": "55", "offsetHeight": 45})
15assert-property: (".mobile-topbar .logo-container img", {"offsetWidth": "35", "offsetHeight": 35})
tests/rustdoc-gui/item-info.goml+1-1
......@@ -20,7 +20,7 @@ store-position: (
2020 {"x": second_line_x, "y": second_line_y},
2121)
2222assert: |first_line_x| != |second_line_x| && |first_line_x| == 521 && |second_line_x| == 277
23assert: |first_line_y| != |second_line_y| && |first_line_y| == 718 && |second_line_y| == 741
23assert: |first_line_y| != |second_line_y| && |first_line_y| == 676 && |second_line_y| == 699
2424
2525// Now we ensure that they're not rendered on the same line.
2626set-window-size: (1100, 800)
tests/rustdoc-gui/mobile-crate-name.goml+7-7
......@@ -5,18 +5,18 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
55// First we change the title to make it big.
66set-window-size: (350, 800)
77// We ensure that the "format" of the title is the same as the one we'll use.
8assert-text: (".mobile-topbar .location a", "test_docs")
8assert-text: ("rustdoc-topbar h2 a", "Crate test_docs")
99// We store the height we know is correct.
10store-property: (".mobile-topbar .location", {"offsetHeight": height})
10store-property: ("rustdoc-topbar h2", {"offsetHeight": height})
1111// We change the crate name to something longer.
12set-text: (".mobile-topbar .location a", "cargo_packager_resource_resolver")
12set-text: ("rustdoc-topbar h2 a", "cargo_packager_resource_resolver")
1313// And we check that the size remained the same.
14assert-property: (".mobile-topbar .location", {"offsetHeight": |height|})
14assert-property: ("rustdoc-topbar h2", {"offsetHeight": |height|})
1515
1616// Now we check if it works for the non-crate pages as well.
1717go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
1818// We store the height we know is correct.
19store-property: (".mobile-topbar .location", {"offsetHeight": height})
20set-text: (".mobile-topbar .location a", "Something_incredibly_long_because")
19store-property: ("rustdoc-topbar h2", {"offsetHeight": height})
20set-text: ("rustdoc-topbar h2 a", "Something_incredibly_long_because")
2121// And we check that the size remained the same.
22assert-property: (".mobile-topbar .location", {"offsetHeight": |height|})
22assert-property: ("rustdoc-topbar h2", {"offsetHeight": |height|})
tests/rustdoc-gui/mobile.goml+1-1
......@@ -5,7 +5,7 @@ set-window-size: (400, 600)
55set-font-size: 18
66wait-for: 100 // wait a bit for the resize and the font-size change to be fully taken into account.
77
8assert-property: (".mobile-topbar h2", {"offsetHeight": 33})
8assert-property: ("rustdoc-topbar h2", {"offsetHeight": 33})
99
1010// On the settings page, the theme buttons should not line-wrap. Instead, they should
1111// all be placed as a group on a line below the setting name "Theme."
tests/rustdoc-gui/notable-trait.goml+13-13
......@@ -82,15 +82,6 @@ call-function: ("check-notable-tooltip-position", {
8282 "i_x": 528,
8383})
8484
85// Checking on mobile now.
86set-window-size: (650, 600)
87wait-for-size: ("body", {"width": 650})
88call-function: ("check-notable-tooltip-position-complete", {
89 "x": 26,
90 "i_x": 305,
91 "popover_x": 0,
92})
93
9485// Now check the colors.
9586define-function: (
9687 "check-colors",
......@@ -176,6 +167,15 @@ call-function: (
176167 },
177168)
178169
170// Checking on mobile now.
171set-window-size: (650, 600)
172wait-for-size: ("body", {"width": 650})
173call-function: ("check-notable-tooltip-position-complete", {
174 "x": 26,
175 "i_x": 305,
176 "popover_x": 0,
177})
178
179179reload:
180180
181181// Check that pressing escape works
......@@ -189,7 +189,7 @@ assert: "#method\.create_an_iterator_from_read .tooltip:focus"
189189// Check that clicking outside works.
190190click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
191191assert-count: ("//*[@class='tooltip popover']", 1)
192click: ".search-input"
192click: ".main-heading h1"
193193assert-count: ("//*[@class='tooltip popover']", 0)
194194assert-false: "#method\.create_an_iterator_from_read .tooltip:focus"
195195
......@@ -219,14 +219,14 @@ define-function: (
219219 store-window-property: {"scrollY": scroll}
220220 click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
221221 wait-for: "//*[@class='tooltip popover']"
222 click: "#settings-menu a"
222 click: ".main-heading h1"
223223 }
224224)
225225
226226// Now we check that the focus isn't given back to the wrong item when opening
227227// another popover.
228228call-function: ("setup-popup", {})
229click: ".search-input"
229click: ".main-heading h1"
230230// We ensure we didn't come back to the previous focused item.
231231assert-window-property-false: {"scrollY": |scroll|}
232232
......@@ -251,7 +251,7 @@ reload:
251251assert-count: ("//*[@class='tooltip popover']", 0)
252252click: "//*[@id='method.create_an_iterator_from_read']//*[@class='tooltip']"
253253assert-count: ("//*[@class='tooltip popover']", 1)
254click: "#settings-menu a"
254click: "rustdoc-toolbar .settings-menu a"
255255wait-for: "#settings"
256256assert-count: ("//*[@class='tooltip popover']", 0)
257257assert-false: "#method\.create_an_iterator_from_read .tooltip:focus"
tests/rustdoc-gui/pocket-menu.goml+44-23
......@@ -3,33 +3,33 @@ include: "utils.goml"
33go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=test"
44wait-for: "#crate-search"
55// First we check that the help menu doesn't exist yet.
6assert-false: "#help-button .popover"
6assert-false: "rustdoc-toolbar .help-menu .popover"
77// Then we display the help menu.
8click: "#help-button"
9assert: "#help-button .popover"
10assert-css: ("#help-button .popover", {"display": "block"})
8click: "rustdoc-toolbar .help-menu"
9assert: "rustdoc-toolbar .help-menu .popover"
10assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
1111
1212// Now we click somewhere else on the page to ensure it is handling the blur event
1313// correctly.
1414click: ".sidebar"
15assert-css: ("#help-button .popover", {"display": "none"})
15assert-false: "rustdoc-toolbar .help-menu .popover"
1616
1717// Now we will check that we cannot have two "pocket menus" displayed at the same time.
18click: "#help-button"
19assert-css: ("#help-button .popover", {"display": "block"})
20click: "#settings-menu"
21assert-css: ("#help-button .popover", {"display": "none"})
22assert-css: ("#settings-menu .popover", {"display": "block"})
18click: "rustdoc-toolbar .help-menu"
19assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
20click: "rustdoc-toolbar .settings-menu"
21assert-false: "rustdoc-toolbar .help-menu .popover"
22assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
2323
2424// Now the other way.
25click: "#help-button"
26assert-css: ("#help-button .popover", {"display": "block"})
27assert-css: ("#settings-menu .popover", {"display": "none"})
25click: "rustdoc-toolbar .help-menu"
26assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
27assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
2828
2929// Now verify that clicking the help menu again closes it.
30click: "#help-button"
31assert-css: ("#help-button .popover", {"display": "none"})
32assert-css: ("#settings-menu .popover", {"display": "none"})
30click: "rustdoc-toolbar .help-menu"
31assert-false: "rustdoc-toolbar .help-menu .popover"
32assert-css: (".settings-menu .popover", {"display": "none"})
3333
3434define-function: (
3535 "check-popover-colors",
......@@ -37,13 +37,21 @@ define-function: (
3737 block {
3838 call-function: ("switch-theme", {"theme": |theme|})
3939
40 click: "#help-button"
40 click: "rustdoc-toolbar .help-menu"
4141 assert-css: (
42 "#help-button .popover",
42 "rustdoc-toolbar .help-menu .popover",
4343 {"display": "block", "border-color": |border_color|},
4444 )
45 compare-elements-css: ("#help-button .popover", "#help-button .top", ["border-color"])
46 compare-elements-css: ("#help-button .popover", "#help-button .bottom", ["border-color"])
45 compare-elements-css: (
46 "rustdoc-toolbar .help-menu .popover",
47 "rustdoc-toolbar .help-menu .top",
48 ["border-color"],
49 )
50 compare-elements-css: (
51 "rustdoc-toolbar .help-menu .popover",
52 "rustdoc-toolbar .help-menu .bottom",
53 ["border-color"],
54 )
4755 }
4856)
4957
......@@ -63,8 +71,21 @@ call-function: ("check-popover-colors", {
6371
6472// Opening the mobile sidebar should close the settings popover.
6573set-window-size: (650, 600)
66click: "#settings-menu a"
67assert-css: ("#settings-menu .popover", {"display": "block"})
74click: "rustdoc-topbar .settings-menu a"
75assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "block"})
76click: ".sidebar-menu-toggle"
77assert: "//*[@class='sidebar shown']"
78assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "none"})
79// Opening the settings popover should close the sidebar.
80click: ".settings-menu a"
81assert-css: ("rustdoc-topbar .settings-menu .popover", {"display": "block"})
82assert-false: "//*[@class='sidebar shown']"
83
84// Opening the settings popover at start (which async loads stuff) should also close.
85reload:
6886click: ".sidebar-menu-toggle"
6987assert: "//*[@class='sidebar shown']"
70assert-css: ("#settings-menu .popover", {"display": "none"})
88assert-false: "rustdoc-topbar .settings-menu .popover"
89click: "rustdoc-topbar .settings-menu a"
90assert-false: "//*[@class='sidebar shown']"
91wait-for: "rustdoc-topbar .settings-menu .popover"
tests/rustdoc-gui/scrape-examples-color.goml+1-1
......@@ -27,7 +27,7 @@ define-function: (
2727 "color": |help_hover_color|,
2828 })
2929 // Moving the cursor to another item to not break next runs.
30 move-cursor-to: ".search-input"
30 move-cursor-to: "#search-button"
3131 }
3232)
3333
tests/rustdoc-gui/scrape-examples-layout.goml+4-4
......@@ -64,8 +64,8 @@ assert-size: (".more-scraped-examples .scraped-example .example-wrap", {
6464store-value: (offset_y, 4)
6565
6666// First with desktop
67assert-position: (".scraped-example", {"y": 256})
68assert-position: (".scraped-example .prev", {"y": 256 + |offset_y|})
67assert-position: (".scraped-example", {"y": 214})
68assert-position: (".scraped-example .prev", {"y": 214 + |offset_y|})
6969
7070// Gradient background should be at the top of the code block.
7171assert-css: (".scraped-example .example-wrap::before", {"top": "0px"})
......@@ -74,8 +74,8 @@ assert-css: (".scraped-example .example-wrap::after", {"bottom": "0px"})
7474// Then with mobile
7575set-window-size: (600, 600)
7676store-size: (".scraped-example .scraped-example-title", {"height": title_height})
77assert-position: (".scraped-example", {"y": 291})
78assert-position: (".scraped-example .prev", {"y": 291 + |offset_y| + |title_height|})
77assert-position: (".scraped-example", {"y": 249})
78assert-position: (".scraped-example .prev", {"y": 249 + |offset_y| + |title_height|})
7979
8080define-function: (
8181 "check_title_and_code_position",
tests/rustdoc-gui/scrape-examples-toggle.goml+1-1
......@@ -25,7 +25,7 @@ define-function: (
2525 // We put the toggle in the original state.
2626 click: ".more-examples-toggle"
2727 // Moving cursor away from the toggle line to prevent disrupting next test.
28 move-cursor-to: ".search-input"
28 move-cursor-to: "rustdoc-toolbar #search-button"
2929 },
3030)
3131
tests/rustdoc-gui/search-about-this-result.goml+2
......@@ -7,6 +7,7 @@ focus: ".search-input"
77press-key: "Enter"
88
99wait-for: "#search-tabs"
10wait-for-false: "#search-tabs .count.loading"
1011assert-count: ("#search-tabs button", 1)
1112assert-count: (".search-results > a", 1)
1213
......@@ -32,6 +33,7 @@ focus: ".search-input"
3233press-key: "Enter"
3334
3435wait-for: "#search-tabs"
36wait-for-false: "#search-tabs .count.loading"
3537assert-text: ("//div[@class='type-signature']", "F -> WhereWhitespace<T>")
3638assert-count: ("#search-tabs button", 1)
3739assert-count: (".search-results > a", 1)
tests/rustdoc-gui/search-corrections.goml+17-58
......@@ -1,101 +1,60 @@
11// ignore-tidy-linelength
2include: "utils.goml"
23
34// Checks that the search tab result tell the user about corrections
45// First, try a search-by-name
56go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
6// Intentionally wrong spelling of "NotableStructWithLongName"
7write-into: (".search-input", "NotableStructWithLongNamr")
8// To be SURE that the search will be run.
9press-key: 'Enter'
10// Waiting for the search results to appear...
11wait-for: "#search-tabs"
7call-function: ("perform-search", {"query": "NotableStructWithLongNamr"})
128
139// Corrections aren't shown on the "In Names" tab.
1410assert: "#search-tabs button.selected:first-child"
15assert-css: (".search-corrections", {
16 "display": "none"
17})
11assert-false: ".search-results:nth-child(1) .search-corrections"
1812
1913// Corrections do get shown on the "In Parameters" tab.
2014click: "#search-tabs button:nth-child(2)"
2115assert: "#search-tabs button.selected:nth-child(2)"
22assert-css: (".search-corrections", {
23 "display": "block"
24})
2516assert-text: (
26 ".search-corrections",
27 "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
17 ".search-results:nth-child(2) .search-corrections",
18 "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"NotableStructWithLongName\" instead."
2819)
2920
3021// Corrections do get shown on the "In Return Type" tab.
3122click: "#search-tabs button:nth-child(3)"
3223assert: "#search-tabs button.selected:nth-child(3)"
33assert-css: (".search-corrections", {
34 "display": "block"
35})
3624assert-text: (
37 ".search-corrections",
38 "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
25 ".search-results:nth-child(3) .search-corrections",
26 "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"NotableStructWithLongName\" instead."
3927)
4028
4129// Now, explicit return values
4230go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
43// Intentionally wrong spelling of "NotableStructWithLongName"
44write-into: (".search-input", "-> NotableStructWithLongNamr")
45// To be SURE that the search will be run.
46press-key: 'Enter'
47// Waiting for the search results to appear...
48wait-for: "#search-tabs"
31call-function: ("perform-search", {"query": "-> NotableStructWithLongNamr"})
4932
50assert-css: (".search-corrections", {
51 "display": "block"
52})
5333assert-text: (
54 ".search-corrections",
55 "Type \"NotableStructWithLongNamr\" not found. Showing results for closest type name \"notablestructwithlongname\" instead."
34 ".search-results.active .search-corrections",
35 "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
5636)
5737
5838// Now, generic correction
5939go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
60// Intentionally wrong spelling of "NotableStructWithLongName"
61write-into: (".search-input", "NotableStructWithLongNamr, NotableStructWithLongNamr")
62// To be SURE that the search will be run.
63press-key: 'Enter'
64// Waiting for the search results to appear...
65wait-for: "#search-tabs"
40call-function: ("perform-search", {"query": "NotableStructWithLongNamr, NotableStructWithLongNamr"})
6641
67assert-css: (".search-corrections", {
68 "display": "block"
69})
7042assert-text: (
71 ".search-corrections",
72 "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
43 ".search-failed.active .search-corrections",
44 "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
7345)
7446
7547// Now, generic correction plus error
7648go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
77// Intentionally wrong spelling of "NotableStructWithLongName"
78write-into: (".search-input", "Foo<NotableStructWithLongNamr>,y")
79// To be SURE that the search will be run.
80press-key: 'Enter'
81// Waiting for the search results to appear...
82wait-for: "#search-tabs"
49call-function: ("perform-search", {"query": "Foo<NotableStructWithLongNamr>,y"})
8350
84assert-css: (".search-corrections", {
85 "display": "block"
86})
8751assert-text: (
88 ".search-corrections",
89 "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"notablestructwithlongname\" instead."
52 ".search-failed.active .search-corrections",
53 "Type \"NotableStructWithLongNamr\" not found and used as generic parameter. Consider searching for \"NotableStructWithLongName\" instead."
9054)
9155
9256go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
93// Intentionally wrong spelling of "NotableStructWithLongName"
94write-into: (".search-input", "generic:NotableStructWithLongNamr<x>,y")
95// To be SURE that the search will be run.
96press-key: 'Enter'
97// Waiting for the search results to appear...
98wait-for: "#search-tabs"
57call-function: ("perform-search", {"query": "generic:NotableStructWithLongNamr<x>,y"})
9958
10059assert-css: (".error", {
10160 "display": "block"
tests/rustdoc-gui/search-error.goml+1
......@@ -8,6 +8,7 @@ define-function: (
88 [theme, error_background],
99 block {
1010 call-function: ("switch-theme", {"theme": |theme|})
11 wait-for-false: "#search-tabs .count.loading"
1112 wait-for: "#search .error code"
1213 assert-css: ("#search .error code", {"background-color": |error_background|})
1314 }
tests/rustdoc-gui/search-filter.goml+7-9
......@@ -2,11 +2,7 @@
22include: "utils.goml"
33go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
44show-text: true
5write-into: (".search-input", "test")
6// To be SURE that the search will be run.
7press-key: 'Enter'
8// Waiting for the search results to appear...
9wait-for: "#search-tabs"
5call-function: ("perform-search", {"query": "test"})
106assert-text: ("#results .externcrate", "test_docs")
117
128wait-for: "#crate-search"
......@@ -21,6 +17,7 @@ press-key: "ArrowDown"
2117press-key: "Enter"
2218// Waiting for the search results to appear...
2319wait-for: "#search-tabs"
20wait-for-false: "#search-tabs .count.loading"
2421assert-document-property: ({"URL": "&filter-crate="}, CONTAINS)
2522// We check that there is no more "test_docs" appearing.
2623assert-false: "#results .externcrate"
......@@ -31,7 +28,8 @@ assert-property: ("#crate-search", {"value": "lib2"})
3128// crate filtering.
3229press-key: "Escape"
3330wait-for-css: ("#main-content", {"display": "block"})
34focus: ".search-input"
31click: "#search-button"
32wait-for: ".search-input"
3533wait-for-css: ("#main-content", {"display": "none"})
3634// We check that there is no more "test_docs" appearing.
3735assert-false: "#results .externcrate"
......@@ -47,6 +45,7 @@ press-key: "ArrowUp"
4745press-key: "Enter"
4846// Waiting for the search results to appear...
4947wait-for: "#search-tabs"
48wait-for-false: "#search-tabs .count.loading"
5049assert-property: ("#crate-search", {"value": "all crates"})
5150
5251// Checking that the URL parameter is taken into account for crate filtering.
......@@ -56,8 +55,7 @@ assert-property: ("#crate-search", {"value": "lib2"})
5655assert-false: "#results .externcrate"
5756
5857// Checking that the text for the "title" is correct (the "all crates" comes from the "<select>").
59assert-text: (".search-results-title", "Results", STARTS_WITH)
60assert-text: (".search-results-title + .sub-heading", " in all crates", STARTS_WITH)
58assert-text: (".search-switcher", "Search results in all crates", STARTS_WITH)
6159
6260// Checking the display of the crate filter.
6361// We start with the light theme.
......@@ -72,7 +70,7 @@ assert-css: ("#crate-search", {
7270})
7371
7472// We now check the dark theme.
75click: "#settings-menu"
73click: "rustdoc-toolbar .settings-menu"
7674wait-for: "#settings"
7775click: "#theme-dark"
7876wait-for-css: ("#crate-search", {
tests/rustdoc-gui/search-form-elements.goml+11-8
......@@ -2,6 +2,7 @@
22include: "utils.goml"
33go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=test"
44wait-for: "#search-tabs" // Waiting for the search.js to load.
5wait-for-false: "#search-tabs .count.loading"
56show-text: true
67
78define-function: (
......@@ -31,7 +32,7 @@ define-function: (
3132 },
3233 )
3334 assert-css: (
34 "#help-button > a",
35 "rustdoc-toolbar .help-menu > a",
3536 {
3637 "color": |menu_button_a_color|,
3738 "border-color": "transparent",
......@@ -39,9 +40,9 @@ define-function: (
3940 },
4041 )
4142 // Hover help button.
42 move-cursor-to: "#help-button"
43 move-cursor-to: "rustdoc-toolbar .help-menu"
4344 assert-css: (
44 "#help-button > a",
45 "rustdoc-toolbar .help-menu > a",
4546 {
4647 "color": |menu_button_a_color|,
4748 "border-color": |menu_button_a_border_hover|,
......@@ -49,15 +50,15 @@ define-function: (
4950 },
5051 )
5152 // Link color inside
52 click: "#help-button"
53 click: "rustdoc-toolbar .help-menu"
5354 assert-css: (
54 "#help a",
55 "rustdoc-toolbar #help a",
5556 {
5657 "color": |menu_a_color|,
5758 },
5859 )
5960 assert-css: (
60 "#settings-menu > a",
61 "rustdoc-toolbar .settings-menu > a",
6162 {
6263 "color": |menu_button_a_color|,
6364 "border-color": "transparent",
......@@ -65,9 +66,9 @@ define-function: (
6566 },
6667 )
6768 // Hover settings menu.
68 move-cursor-to: "#settings-menu"
69 move-cursor-to: "rustdoc-toolbar .settings-menu"
6970 assert-css: (
70 "#settings-menu:hover > a",
71 "rustdoc-toolbar .settings-menu:hover > a",
7172 {
7273 "color": |menu_button_a_color|,
7374 "border-color": |menu_button_a_border_hover|,
......@@ -120,8 +121,10 @@ call-function: (
120121// Check that search input correctly decodes form encoding.
121122go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a+b"
122123wait-for: "#search-tabs" // Waiting for the search.js to load.
124wait-for-false: "#search-tabs .count.loading"
123125assert-property: (".search-input", { "value": "a b" })
124126// Check that literal + is not treated as space.
125127go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=a%2Bb"
126128wait-for: "#search-tabs" // Waiting for the search.js to load.
129wait-for-false: "#search-tabs .count.loading"
127130assert-property: (".search-input", { "value": "a+b" })
tests/rustdoc-gui/search-input-mobile.goml+8-5
......@@ -2,10 +2,13 @@
22// The PR which fixed it is: https://github.com/rust-lang/rust/pull/81592
33go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
44set-window-size: (463, 700)
5// We first check that the search input isn't already focused.
6assert-false: ("input.search-input:focus")
7click: "input.search-input"
5click: "#search-button"
6wait-for: ".search-input"
7assert: "input.search-input:focus"
8
9go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
810reload:
911set-window-size: (750, 700)
10click: "input.search-input"
11assert: ("input.search-input:focus")
12click: "#search-button"
13wait-for: ".search-input"
14assert: "input.search-input:focus"
tests/rustdoc-gui/search-keyboard.goml+9-12
......@@ -1,28 +1,25 @@
11// Checks that the search tab results work correctly with function signature syntax
22// First, try a search-by-name
3include: "utils.goml"
34go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
4write-into: (".search-input", "Foo")
5// To be SURE that the search will be run.
6press-key: 'Enter'
7// Waiting for the search results to appear...
8wait-for: "#search-tabs"
5call-function: ("perform-search", {"query": "Foo"})
96
107// Now use the keyboard commands to switch to the third result.
118press-key: "ArrowDown"
129press-key: "ArrowDown"
1310press-key: "ArrowDown"
14assert: ".search-results.active > a:focus:nth-of-type(3)"
11wait-for: ".search-results.active > a:focus:nth-of-type(3)"
1512
1613// Now switch to the second tab, then back to the first one, then arrow back up.
1714press-key: "ArrowRight"
18assert: ".search-results.active:nth-of-type(2) > a:focus:nth-of-type(1)"
15wait-for: ".search-results.active:nth-of-type(2) > a:focus:nth-of-type(1)"
1916press-key: "ArrowLeft"
20assert: ".search-results.active:nth-of-type(1) > a:focus:nth-of-type(3)"
17wait-for: ".search-results.active:nth-of-type(1) > a:focus:nth-of-type(3)"
2118press-key: "ArrowUp"
22assert: ".search-results.active > a:focus:nth-of-type(2)"
19wait-for: ".search-results.active > a:focus:nth-of-type(2)"
2320press-key: "ArrowUp"
24assert: ".search-results.active > a:focus:nth-of-type(1)"
21wait-for: ".search-results.active > a:focus:nth-of-type(1)"
2522press-key: "ArrowUp"
26assert: ".search-input:focus"
23wait-for: ".search-input:focus"
2724press-key: "ArrowDown"
28assert: ".search-results.active > a:focus:nth-of-type(1)"
25wait-for: ".search-results.active > a:focus:nth-of-type(1)"
tests/rustdoc-gui/search-reexport.goml+4-7
......@@ -6,10 +6,8 @@ call-function: ("switch-theme", {"theme": "dark"})
66// First we check that the reexport has the correct ID and no background color.
77assert-text: ("//*[@id='reexport.TheStdReexport']", "pub use ::std as TheStdReexport;")
88assert-css: ("//*[@id='reexport.TheStdReexport']", {"background-color": "rgba(0, 0, 0, 0)"})
9write-into: (".search-input", "TheStdReexport")
10// To be SURE that the search will be run.
11press-key: 'Enter'
12wait-for: "//a[@class='result-import']"
9call-function: ("perform-search", {"query": "TheStdReexport"})
10assert: "//a[@class='result-import']"
1311assert-attribute: (
1412 "//a[@class='result-import']",
1513 {"href": "../test_docs/index.html#reexport.TheStdReexport"},
......@@ -21,9 +19,8 @@ wait-for-css: ("//*[@id='reexport.TheStdReexport']", {"background-color": "#494a
2119
2220// We now check that the alias is working as well on the reexport.
2321// To be SURE that the search will be run.
24press-key: 'Enter'
25write-into: (".search-input", "AliasForTheStdReexport")
26wait-for: "//a[@class='result-import']"
22call-function: ("perform-search", {"query": "AliasForTheStdReexport"})
23assert: "//a[@class='result-import']"
2724assert-text: (
2825 "a.result-import .result-name",
2926 "re-export AliasForTheStdReexport - see test_docs::TheStdReexport",
tests/rustdoc-gui/search-result-color.goml+2-5
......@@ -14,6 +14,7 @@ define-function: (
1414
1515 // Waiting for the search results to appear...
1616 wait-for: "#search-tabs"
17 wait-for-false: "#search-tabs .count.loading"
1718 assert-css: (
1819 "#search-tabs > button > .count",
1920 {"color": |count_color|},
......@@ -212,11 +213,7 @@ call-function: ("check-search-color", {
212213// Check the alias.
213214go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
214215
215write-into: (".search-input", "thisisanalias")
216// To be SURE that the search will be run.
217press-key: 'Enter'
218// Waiting for the search results to appear...
219wait-for: "#search-tabs"
216call-function: ("perform-search", {"query": "thisisanalias"})
220217
221218define-function: (
222219 "check-alias",
tests/rustdoc-gui/search-result-description.goml+1
......@@ -2,4 +2,5 @@
22go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=some_more_function"
33// Waiting for the search results to appear...
44wait-for: "#search-tabs"
5wait-for-false: "#search-tabs .count.loading"
56assert-text: (".search-results .desc code", "format!")
tests/rustdoc-gui/search-result-display.goml+6-3
......@@ -7,6 +7,7 @@ write-into: (".search-input", "test")
77// To be SURE that the search will be run.
88press-key: 'Enter'
99wait-for: "#crate-search"
10wait-for-false: "#search-tabs .count.loading"
1011// The width is returned by "getComputedStyle" which returns the exact number instead of the
1112// CSS rule which is "50%"...
1213assert-size: (".search-results div.desc", {"width": 248})
......@@ -34,6 +35,7 @@ assert: |new_width| < |width| - 10
3435// Check that if the search is too long on mobile, it'll go under the "typename".
3536go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName"
3637wait-for: "#crate-search"
38wait-for-false: "#search-tabs .count.loading"
3739compare-elements-position-near: (
3840 ".search-results .result-name .typename",
3941 ".search-results .result-name .path",
......@@ -51,7 +53,7 @@ set-window-size: (900, 900)
5153
5254// First we check the current width, height and position.
5355assert-css: ("#crate-search", {"width": "159px"})
54store-size: (".search-results-title", {
56store-size: (".search-switcher", {
5557 "height": search_results_title_height,
5658 "width": search_results_title_width,
5759})
......@@ -64,8 +66,8 @@ set-text: (
6466)
6567
6668// Then we compare again to confirm the height didn't change.
67assert-size: ("#crate-search", {"width": 370})
68assert-size: (".search-results-title", {
69assert-size: ("#crate-search", {"width": 185})
70assert-size: (".search-switcher", {
6971 "height": |search_results_title_height|,
7072})
7173assert-css: ("#search", {"width": "640px"})
......@@ -79,6 +81,7 @@ define-function: (
7981 block {
8082 call-function: ("switch-theme", {"theme": |theme|})
8183 wait-for: "#crate-search"
84 wait-for-false: "#search-tabs .count.loading"
8285 assert-css: ("#crate-search", {"border": "1px solid " + |border|})
8386 assert-css: ("#crate-search-div::after", {"filter": |filter|})
8487 move-cursor-to: "#crate-search"
tests/rustdoc-gui/search-result-go-to-first.goml+2-1
......@@ -9,6 +9,7 @@ assert-text-false: (".main-heading h1", "Struct test_docs::FooCopy item path")
99go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=struct%3AFoo"
1010// Waiting for the search results to appear...
1111wait-for: "#search-tabs"
12wait-for-false: "#search-tabs .count.loading"
1213assert-text-false: (".main-heading h1", "Struct test_docs::FooCopy item path")
1314// Ensure that the search results are displayed, not the "normal" content.
1415assert-css: ("#main-content", {"display": "none"})
......@@ -17,4 +18,4 @@ assert-css: ("#main-content", {"display": "none"})
1718go-to: "file://" + |DOC_PATH| + "/test_docs/index.html?search=struct%3AFoo&go_to_first=true"
1819// Waiting for the page to load...
1920wait-for-text: (".main-heading .rustdoc-breadcrumbs", "test_docs")
20wait-for-text: (".main-heading h1", "Struct FooCopy item path")
21wait-for-text: (".main-heading h1", "Struct Foo Copy item path")
tests/rustdoc-gui/search-result-impl-disambiguation.goml+5-10
......@@ -1,15 +1,12 @@
11// ignore-tidy-linelength
2include: "utils.goml"
23
34// Checks that, if a type has two methods with the same name, they both get
45// linked correctly.
56go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
67
78// This should link to the inherent impl
8write-into: (".search-input", "ZyxwvutMethodDisambiguation -> bool")
9// To be SURE that the search will be run.
10press-key: 'Enter'
11// Waiting for the search results to appear...
12wait-for: "#search-tabs"
9call-function: ("perform-search", {"query": "ZyxwvutMethodDisambiguation -> bool"})
1310// Check the disambiguated link.
1411assert-count: ("a.result-method", 1)
1512assert-attribute: ("a.result-method", {
......@@ -25,11 +22,7 @@ assert: "section:target"
2522go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
2623
2724// This should link to the trait impl
28write-into: (".search-input", "ZyxwvutMethodDisambiguation, usize -> usize")
29// To be SURE that the search will be run.
30press-key: 'Enter'
31// Waiting for the search results to appear...
32wait-for: "#search-tabs"
25call-function: ("perform-search", {"query": "ZyxwvutMethodDisambiguation, usize -> usize"})
3326// Check the disambiguated link.
3427assert-count: ("a.result-method", 1)
3528assert-attribute: ("a.result-method", {
......@@ -47,6 +40,7 @@ assert: "section:target"
4740// impl block's disambiguator is also acted upon.
4841go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=MultiImplBlockStruct->bool"
4942wait-for: "#search-tabs"
43wait-for-false: "#search-tabs .count.loading"
5044assert-count: ("a.result-method", 1)
5145assert-attribute: ("a.result-method", {
5246 "href": "../lib2/another_mod/struct.MultiImplBlockStruct.html#impl-MultiImplBlockStruct/method.second_fn"
......@@ -56,6 +50,7 @@ wait-for: "details:has(summary > #impl-MultiImplBlockStruct-1) > div section[id=
5650
5751go-to: "file://" + |DOC_PATH| + "/lib2/index.html?search=MultiImplBlockStruct->u32"
5852wait-for: "#search-tabs"
53wait-for-false: "#search-tabs .count.loading"
5954assert-count: ("a.result-method", 1)
6055assert-attribute: ("a.result-method", {
6156 "href": "../lib2/another_mod/struct.MultiImplBlockStruct.html#impl-MultiImplBlockTrait-for-MultiImplBlockStruct/method.second_fn"
tests/rustdoc-gui/search-result-keyword.goml+2-5
......@@ -1,8 +1,5 @@
11// Checks that the "keyword" results have the expected text alongside them.
2include: "utils.goml"
23go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
3write-into: (".search-input", "for")
4// To be SURE that the search will be run.
5press-key: 'Enter'
6// Waiting for the search results to appear...
7wait-for: "#search-tabs"
4call-function: ("perform-search", {"query": "for"})
85assert-text: (".result-keyword .result-name", "keyword for")
tests/rustdoc-gui/search-tab-change-title-fn-sig.goml+7-25
......@@ -1,11 +1,9 @@
11// Checks that the search tab results work correctly with function signature syntax
22// First, try a search-by-name
3include: "utils.goml"
34go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
4write-into: (".search-input", "Foo")
5// To be SURE that the search will be run.
6press-key: 'Enter'
7// Waiting for the search results to appear...
8wait-for: "#search-tabs"
5call-function: ("perform-search", {"query": "Foo"})
6
97assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
108assert-text: ("#search-tabs > button:nth-of-type(1)", "In Names", STARTS_WITH)
119assert: "input.search-input:focus"
......@@ -23,11 +21,7 @@ wait-for-attribute: ("#search-tabs > button:nth-of-type(3)", {"class": "selected
2321
2422// Now try search-by-return
2523go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
26write-into: (".search-input", "-> String")
27// To be SURE that the search will be run.
28press-key: 'Enter'
29// Waiting for the search results to appear...
30wait-for: "#search-tabs"
24call-function: ("perform-search", {"query": "-> String"})
3125assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
3226assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Return Types", STARTS_WITH)
3327assert: "input.search-input:focus"
......@@ -45,30 +39,18 @@ wait-for-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected
4539
4640// Try with a search-by-return with no results
4741go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
48write-into: (".search-input", "-> Something")
49// To be SURE that the search will be run.
50press-key: 'Enter'
51// Waiting for the search results to appear...
52wait-for: "#search-tabs"
42call-function: ("perform-search", {"query": "-> Something"})
5343assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
5444assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Return Types", STARTS_WITH)
5545
5646// Try with a search-by-parameter
5747go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
58write-into: (".search-input", "usize,pattern")
59// To be SURE that the search will be run.
60press-key: 'Enter'
61// Waiting for the search results to appear...
62wait-for: "#search-tabs"
48call-function: ("perform-search", {"query": "usize,pattern"})
6349assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
6450assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Parameters", STARTS_WITH)
6551
6652// Try with a search-by-parameter-and-return
6753go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
68write-into: (".search-input", "pattern -> str")
69// To be SURE that the search will be run.
70press-key: 'Enter'
71// Waiting for the search results to appear...
72wait-for: "#search-tabs"
54call-function: ("perform-search", {"query": "pattern -> str"})
7355assert-attribute: ("#search-tabs > button:nth-of-type(1)", {"class": "selected"})
7456assert-text: ("#search-tabs > button:nth-of-type(1)", "In Function Signatures", STARTS_WITH)
tests/rustdoc-gui/search-tab.goml+2-1
......@@ -15,7 +15,8 @@ define-function: (
1515 focus: ".search-input"
1616 press-key: "Enter"
1717
18 wait-for: "#search-tabs"
18 wait-for: "#search-tabs .count"
19 wait-for-false: "#search-tabs .count.loading"
1920 assert-css: ("#search-tabs > button:not(.selected)", {
2021 "background-color": |background|,
2122 "border-bottom": |border_bottom|,
tests/rustdoc-gui/search-title.goml+2-4
......@@ -5,10 +5,7 @@ go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
55store-value: (title, "test_docs - Rust")
66assert-document-property: {"title": |title|}
77
8write-into: (".search-input", "test")
9// To be SURE that the search will be run.
10press-key: 'Enter'
11wait-for: "#crate-search"
8call-function: ("perform-search", {"query": "test"})
129
1310assert-document-property: {"title": '"test" Search - Rust'}
1411
......@@ -16,6 +13,7 @@ set-property: (".search-input", {"value": "another one"})
1613// To be SURE that the search will be run.
1714press-key: 'Enter'
1815wait-for: "#crate-search"
16wait-for-false: "#search-tabs .count.loading"
1917
2018assert-document-property: {"title": '"another one" Search - Rust'}
2119
tests/rustdoc-gui/setting-auto-hide-content-large-items.goml+1-1
......@@ -9,7 +9,7 @@ define-function: (
99 [storage_value, setting_attribute_value, toggle_attribute_value],
1010 block {
1111 assert-local-storage: {"rustdoc-auto-hide-large-items": |storage_value|}
12 click: "#settings-menu"
12 click: "rustdoc-toolbar .settings-menu"
1313 wait-for: "#settings"
1414 assert-property: ("#auto-hide-large-items", {"checked": |setting_attribute_value|})
1515 assert-attribute: (".item-decl .type-contents-toggle", {"open": |toggle_attribute_value|})
tests/rustdoc-gui/setting-auto-hide-item-methods-docs.goml+1-1
......@@ -6,7 +6,7 @@ define-function: (
66 [storage_value, setting_attribute_value, toggle_attribute_value],
77 block {
88 assert-local-storage: {"rustdoc-auto-hide-method-docs": |storage_value|}
9 click: "#settings-menu"
9 click: "rustdoc-toolbar .settings-menu"
1010 wait-for: "#settings"
1111 assert-property: ("#auto-hide-method-docs", {"checked": |setting_attribute_value|})
1212 assert-attribute: (".toggle.method-toggle", {"open": |toggle_attribute_value|})
tests/rustdoc-gui/setting-auto-hide-trait-implementations.goml+1-1
......@@ -5,7 +5,7 @@ define-function: (
55 [storage_value, setting_attribute_value, toggle_attribute_value],
66 block {
77 assert-local-storage: {"rustdoc-auto-hide-trait-implementations": |storage_value|}
8 click: "#settings-menu"
8 click: "rustdoc-toolbar .settings-menu"
99 wait-for: "#settings"
1010 assert-property: ("#auto-hide-trait-implementations", {"checked": |setting_attribute_value|})
1111 assert-attribute: ("#trait-implementations-list > details", {"open": |toggle_attribute_value|}, ALL)
tests/rustdoc-gui/setting-go-to-only-result.goml+2-2
......@@ -5,7 +5,7 @@ define-function: (
55 [storage_value, setting_attribute_value],
66 block {
77 assert-local-storage: {"rustdoc-go-to-only-result": |storage_value|}
8 click: "#settings-menu"
8 click: "rustdoc-toolbar .settings-menu"
99 wait-for: "#settings"
1010 assert-property: ("#go-to-only-result", {"checked": |setting_attribute_value|})
1111 }
......@@ -25,7 +25,7 @@ wait-for: "#search"
2525assert-document-property: ({"URL": "/lib2/index.html"}, CONTAINS)
2626
2727// Now we change its value.
28click: "#settings-menu"
28click: "rustdoc-toolbar .settings-menu"
2929wait-for: "#settings"
3030click: "#go-to-only-result"
3131assert-local-storage: {"rustdoc-go-to-only-result": "true"}
tests/rustdoc-gui/settings-button.goml+1-1
......@@ -9,7 +9,7 @@ define-function: (
99 [theme, filter],
1010 block {
1111 call-function: ("switch-theme", {"theme": |theme|})
12 assert-css: ("#settings-menu > a::before", {
12 assert-css: ("rustdoc-toolbar .settings-menu > a::before", {
1313 "filter": |filter|,
1414 "width": "18px",
1515 "height": "18px",
tests/rustdoc-gui/settings.goml+21-21
......@@ -5,7 +5,7 @@ show-text: true // needed when we check for colors below.
55// First, we check that the settings page doesn't exist.
66assert-false: "#settings"
77// We now click on the settings button.
8click: "#settings-menu"
8click: "rustdoc-toolbar .settings-menu"
99wait-for: "#settings"
1010assert-css: ("#settings", {"display": "block"})
1111
......@@ -13,11 +13,11 @@ assert-css: ("#settings", {"display": "block"})
1313store-css: (".setting-line", {"margin": setting_line_margin})
1414
1515// Let's close it by clicking on the same button.
16click: "#settings-menu"
16click: "rustdoc-toolbar .settings-menu"
1717wait-for-css: ("#settings", {"display": "none"})
1818
1919// Let's check that pressing "ESCAPE" is closing it.
20click: "#settings-menu"
20click: "rustdoc-toolbar .settings-menu"
2121wait-for-css: ("#settings", {"display": "block"})
2222press-key: "Escape"
2323wait-for-css: ("#settings", {"display": "none"})
......@@ -28,7 +28,7 @@ write: "test"
2828// To be SURE that the search will be run.
2929press-key: 'Enter'
3030wait-for: "#alternative-display #search"
31click: "#settings-menu"
31click: "rustdoc-toolbar .settings-menu"
3232wait-for-css: ("#settings", {"display": "block"})
3333// Ensure that the search is still displayed.
3434wait-for: "#alternative-display #search"
......@@ -41,7 +41,7 @@ set-local-storage: {"rustdoc-theme": "dark", "rustdoc-use-system-theme": "false"
4141// We reload the page so the local storage settings are being used.
4242reload:
4343
44click: "#settings-menu"
44click: "rustdoc-toolbar .settings-menu"
4545wait-for: "#settings"
4646
4747// We check that the "Use system theme" is disabled.
......@@ -55,7 +55,7 @@ assert: "#preferred-light-theme.setting-line.hidden"
5555assert-property: ("#theme .setting-radio-choices #theme-dark", {"checked": "true"})
5656
5757// Some style checks...
58move-cursor-to: "#settings-menu > a"
58move-cursor-to: "rustdoc-toolbar .settings-menu > a"
5959// First we check the "default" display for radio buttons.
6060assert-css: (
6161 "#theme-dark",
......@@ -194,7 +194,7 @@ assert-css: (
194194 "border-width": "2px",
195195 },
196196)
197move-cursor-to: "#settings-menu > a"
197move-cursor-to: "rustdoc-toolbar .settings-menu > a"
198198// Let's now check with the focus for toggles.
199199focus: "#auto-hide-large-items"
200200assert-css: (
......@@ -273,43 +273,43 @@ assert-local-storage: {"rustdoc-disable-shortcuts": "true"}
273273press-key: "Escape"
274274press-key: "?"
275275assert-false: "#help-button .popover"
276wait-for-css: ("#settings-menu .popover", {"display": "block"})
276wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
277277
278278// Now turn keyboard shortcuts back on, and see if they work.
279279click: "#disable-shortcuts"
280280assert-local-storage: {"rustdoc-disable-shortcuts": "false"}
281281press-key: "Escape"
282282press-key: "?"
283wait-for-css: ("#help-button .popover", {"display": "block"})
284assert-css: ("#settings-menu .popover", {"display": "none"})
283wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
284assert-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
285285
286286// Now switch back to the settings popover, and make sure the keyboard
287287// shortcut works when a check box is selected.
288click: "#settings-menu > a"
289wait-for-css: ("#settings-menu .popover", {"display": "block"})
288click: "rustdoc-toolbar .settings-menu > a"
289wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
290290focus: "#auto-hide-large-items"
291291press-key: "?"
292wait-for-css: ("#settings-menu .popover", {"display": "none"})
293wait-for-css: ("#help-button .popover", {"display": "block"})
292wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
293wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
294294
295295// Now switch back to the settings popover, and make sure the keyboard
296296// shortcut works when a check box is selected.
297click: "#settings-menu > a"
298wait-for-css: ("#settings-menu .popover", {"display": "block"})
299wait-for-css: ("#help-button .popover", {"display": "none"})
297click: "rustdoc-toolbar .settings-menu > a"
298wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "block"})
299assert-false: "rustdoc-toolbar .help-menu .popover"
300300focus: "#theme-system-preference"
301301press-key: "?"
302wait-for-css: ("#settings-menu .popover", {"display": "none"})
303wait-for-css: ("#help-button .popover", {"display": "block"})
302wait-for-css: ("rustdoc-toolbar .settings-menu .popover", {"display": "none"})
303wait-for-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
304304
305305// Now we go to the settings page to check that the CSS is loaded as expected.
306306go-to: "file://" + |DOC_PATH| + "/settings.html"
307307wait-for: "#settings"
308assert-false: "#settings-menu"
308assert-false: "rustdoc-toolbar .settings-menu"
309309assert-css: (".setting-radio", {"cursor": "pointer"})
310310
311311assert-attribute-false: ("#settings", {"class": "popover"}, CONTAINS)
312compare-elements-position: (".sub form", "#settings", ["x"])
312compare-elements-position: (".main-heading", "#settings", ["x"])
313313
314314// Check that setting-line has the same margin in this mode as in the popover.
315315assert-css: (".setting-line", {"margin": |setting_line_margin|})
tests/rustdoc-gui/shortcuts.goml+2-2
......@@ -8,9 +8,9 @@ press-key: "Escape"
88assert-false: "input.search-input:focus"
99// We now check for the help popup.
1010press-key: "?"
11assert-css: ("#help-button .popover", {"display": "block"})
11assert-css: ("rustdoc-toolbar .help-menu .popover", {"display": "block"})
1212press-key: "Escape"
13assert-css: ("#help-button .popover", {"display": "none"})
13assert-false: "rustdoc-toolbar .help-menu .popover"
1414// Checking doc collapse and expand.
1515// It should be displaying a "-":
1616assert-text: ("#toggle-all-docs", "Summary")
tests/rustdoc-gui/sidebar-mobile.goml+4-4
......@@ -17,7 +17,7 @@ assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
1717focus: ".sidebar-elems h3 a"
1818assert-css: (".sidebar", {"display": "block", "left": "0px"})
1919// When we tab out of the sidebar, close it.
20focus: ".search-input"
20focus: "#search-button"
2121assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
2222
2323// Open the sidebar menu.
......@@ -43,7 +43,7 @@ press-key: "Escape"
4343assert-css: (".sidebar", {"display": "block", "left": "-1000px"})
4444
4545// Check that the topbar is visible
46assert-property: (".mobile-topbar", {"clientHeight": "45"})
46assert-property: ("rustdoc-topbar", {"clientHeight": "45"})
4747
4848// Check that clicking an element from the sidebar scrolls to the right place
4949// so the target is not obscured by the topbar.
......@@ -54,7 +54,7 @@ assert-position: ("#method\.must_use", {"y": 46})
5454// Check that the bottom-most item on the sidebar menu can be scrolled fully into view.
5555click: ".sidebar-menu-toggle"
5656scroll-to: ".block.keyword li:nth-child(1)"
57compare-elements-position-near: (".block.keyword li:nth-child(1)", ".mobile-topbar", {"y": 544})
57compare-elements-position-near: (".block.keyword li:nth-child(1)", "rustdoc-topbar", {"y": 544})
5858
5959// Now checking the background color of the sidebar.
6060// Close the sidebar menu.
......@@ -65,7 +65,7 @@ define-function: (
6565 "check-colors",
6666 [theme, color, background],
6767 block {
68 call-function: ("switch-theme", {"theme": |theme|})
68 call-function: ("switch-theme-mobile", {"theme": |theme|})
6969 reload:
7070
7171 // Open the sidebar menu.
tests/rustdoc-gui/sidebar-resize-close-popover.goml+2-2
......@@ -2,7 +2,7 @@
22go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
33assert-property: (".sidebar", {"clientWidth": "199"})
44show-text: true
5click: "#settings-menu"
5click: "rustdoc-toolbar .settings-menu"
66wait-for: "#settings"
77assert-css: ("#settings", {"display": "block"})
88// normal resizing
......@@ -12,7 +12,7 @@ assert-css: ("#settings", {"display": "none"})
1212
1313// Now same thing, but for source code
1414go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
15click: "#settings-menu"
15click: "rustdoc-toolbar .settings-menu"
1616wait-for: "#settings"
1717assert-css: ("#settings", {"display": "block"})
1818assert-property: (".sidebar", {"clientWidth": "49"})
tests/rustdoc-gui/sidebar-resize-setting.goml+12-12
......@@ -4,7 +4,7 @@ assert-property: (".sidebar", {"clientWidth": "199"})
44show-text: true
55
66// Verify that the "hide" option is unchecked
7click: "#settings-menu"
7click: "rustdoc-toolbar .settings-menu"
88wait-for: "#settings"
99assert-css: ("#settings", {"display": "block"})
1010assert-property: ("#hide-sidebar", {"checked": "false"})
......@@ -15,7 +15,7 @@ drag-and-drop: ((205, 100), (5, 100))
1515assert-css: (".sidebar", {"display": "none"})
1616
1717// Verify that the "hide" option is checked
18focus: "#settings-menu a"
18focus: "rustdoc-toolbar .settings-menu a"
1919press-key: "Enter"
2020wait-for-css: ("#settings", {"display": "block"})
2121assert-property: ("#hide-sidebar", {"checked": "true"})
......@@ -24,28 +24,28 @@ wait-for-css: (".sidebar", {"display": "block"})
2424
2525// Verify that hiding the sidebar hides the source sidebar
2626// and puts the button in static position mode on mobile
27go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
27go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
2828set-window-size: (600, 600)
29focus: "#settings-menu a"
29focus: "rustdoc-topbar .settings-menu a"
3030press-key: "Enter"
3131wait-for-css: ("#settings", {"display": "block"})
32wait-for-css: ("#sidebar-button", {"position": "static"})
33assert-property: ("#hide-sidebar", {"checked": "false"})
34click: "#hide-sidebar"
35wait-for-css: (".sidebar", {"display": "none"})
3236wait-for-css: ("#sidebar-button", {"position": "fixed"})
3337store-position: ("#sidebar-button", {
3438 "y": sidebar_button_y,
3539 "x": sidebar_button_x,
3640})
37assert-property: ("#hide-sidebar", {"checked": "false"})
38click: "#hide-sidebar"
39wait-for-css: (".sidebar", {"display": "none"})
40wait-for-css: ("#sidebar-button", {"position": "static"})
41assert-position: ("#sidebar-button", {
42 "y": |sidebar_button_y|,
43 "x": |sidebar_button_x|,
44})
4541assert-property: ("#hide-sidebar", {"checked": "true"})
4642press-key: "Escape"
4743// Clicking the sidebar button should work, and implicitly re-enable
4844// the persistent navigation bar
4945wait-for-css: ("#settings", {"display": "none"})
46assert-position: ("#sidebar-button", {
47 "y": |sidebar_button_y|,
48 "x": |sidebar_button_x|,
49})
5050click: "#sidebar-button"
5151wait-for-css: (".sidebar", {"display": "block"})
tests/rustdoc-gui/sidebar-source-code-display.goml+1-1
......@@ -141,7 +141,7 @@ click: "#sidebar-button"
141141wait-for-css: (".src .sidebar > *", {"visibility": "hidden"})
142142// We scroll to line 117 to change the scroll position.
143143scroll-to: '//*[@id="117"]'
144store-value: (y_offset, "2578")
144store-value: (y_offset, "2567")
145145assert-window-property: {"pageYOffset": |y_offset|}
146146// Expanding the sidebar...
147147click: "#sidebar-button"
tests/rustdoc-gui/sidebar-source-code.goml+1-1
......@@ -85,4 +85,4 @@ assert-false: ".src-sidebar-expanded"
8585assert: "nav.sidebar"
8686
8787// Check that the topbar is not visible
88assert-false: ".mobile-topbar"
88assert-false: "rustdoc-topbar"
tests/rustdoc-gui/sidebar.goml+3-3
......@@ -200,7 +200,7 @@ drag-and-drop: ((205, 100), (108, 100))
200200assert-position: (".sidebar-crate > h2 > a", {"x": -3})
201201
202202// Check that the mobile sidebar and the source sidebar use the same icon.
203store-css: (".mobile-topbar .sidebar-menu-toggle::before", {"content": image_url})
203store-css: ("rustdoc-topbar .sidebar-menu-toggle::before", {"content": image_url})
204204// Then we go to a source page.
205205click: ".main-heading .src"
206206assert-css: ("#sidebar-button a::before", {"content": |image_url|})
......@@ -212,7 +212,7 @@ assert: |sidebar_background| != |sidebar_background_hover|
212212click: "#sidebar-button a"
213213wait-for: "html.src-sidebar-expanded"
214214assert-css: ("#sidebar-button a:hover", {"background-color": |sidebar_background_hover|})
215move-cursor-to: "#settings-menu"
215move-cursor-to: "#search-button"
216216assert-css: ("#sidebar-button a:not(:hover)", {"background-color": |sidebar_background|})
217217// Closing sidebar.
218218click: "#sidebar-button a"
......@@ -220,7 +220,7 @@ wait-for: "html:not(.src-sidebar-expanded)"
220220// Now we check the same when the sidebar button is moved alongside the search.
221221set-window-size: (500, 500)
222222store-css: ("#sidebar-button a:hover", {"background-color": not_sidebar_background_hover})
223move-cursor-to: "#settings-menu"
223move-cursor-to: "rustdoc-toolbar #search-button"
224224store-css: ("#sidebar-button a:not(:hover)", {"background-color": not_sidebar_background})
225225// The sidebar background is supposed to be the same as the main background.
226226assert-css: ("body", {"background-color": |not_sidebar_background|})
tests/rustdoc-gui/source-anchor-scroll.goml+4-4
......@@ -8,13 +8,13 @@ set-window-size: (600, 800)
88assert-property: ("html", {"scrollTop": "0"})
99
1010click: '//a[text() = "barbar" and @href="#5-7"]'
11assert-property: ("html", {"scrollTop": "206"})
11assert-property: ("html", {"scrollTop": "195"})
1212click: '//a[text() = "bar" and @href="#28-36"]'
13assert-property: ("html", {"scrollTop": "239"})
13assert-property: ("html", {"scrollTop": "228"})
1414click: '//a[normalize-space() = "sub_fn" and @href="#2-4"]'
15assert-property: ("html", {"scrollTop": "134"})
15assert-property: ("html", {"scrollTop": "123"})
1616
1717// We now check that clicking on lines doesn't change the scroll
1818// Extra information: the "sub_fn" function header is on line 1.
1919click: '//*[@id="6"]'
20assert-property: ("html", {"scrollTop": "134"})
20assert-property: ("html", {"scrollTop": "123"})
tests/rustdoc-gui/source-code-page.goml+5-27
......@@ -89,9 +89,9 @@ assert-css: ("a[data-nosnippet]", {"text-align": "right"}, ALL)
8989// do anything (and certainly not add a `#NaN` to the URL!).
9090go-to: "file://" + |DOC_PATH| + "/src/test_docs/lib.rs.html"
9191// We use this assert-position to know where we will click.
92assert-position: ("//*[@id='1']", {"x": 81, "y": 169})
93// We click on the left of the "1" anchor but still in the `a[data-nosnippet]`.
94click: (77, 163)
92assert-position: ("//*[@id='1']", {"x": 81, "y": 141})
93// We click on the left of the "1" anchor but still in the "src-line-number" `<pre>`.
94click: (135, 77)
9595assert-document-property: ({"URL": "/lib.rs.html"}, ENDS_WITH)
9696
9797// Checking the source code sidebar.
......@@ -156,27 +156,8 @@ call-function: ("check-sidebar-dir-entry", {
156156 "y": |source_sidebar_title_y| + |source_sidebar_title_height| + 7,
157157})
158158
159// Check the search form
160assert-css: ("nav.sub", {"flex-direction": "row"})
161// The goal of this test is to ensure the search input is perfectly centered
162// between the top of the page and the header.
163// To check this, we maintain the invariant:
164//
165// offsetTop[nav.sub form] = offsetTop[#main-content] - offsetHeight[nav.sub form] - offsetTop[nav.sub form]
166assert-position: ("nav.sub form", {"y": 15})
167assert-property: ("nav.sub form", {"offsetHeight": 34})
168assert-position: ("h1", {"y": 68})
169// 15 = 64 - 34 - 15
170
171// Now do the same check on moderately-sized, tablet mobile.
172set-window-size: (700, 700)
173assert-css: ("nav.sub", {"flex-direction": "row"})
174assert-position: ("nav.sub form", {"y": 8})
175assert-property: ("nav.sub form", {"offsetHeight": 34})
176assert-position: ("h1", {"y": 54})
177// 8 = 50 - 34 - 8
178
179159// Check the sidebar directory entries have a marker and spacing (tablet).
160set-window-size: (700, 700)
180161store-property: (".src-sidebar-title", {
181162 "offsetHeight": source_sidebar_title_height,
182163 "offsetTop": source_sidebar_title_y,
......@@ -187,11 +168,8 @@ call-function: ("check-sidebar-dir-entry", {
187168 "y": |source_sidebar_title_y| + |source_sidebar_title_height| + 7,
188169})
189170
190// Tiny, phone mobile gets a different display where the logo is stacked on top.
191set-window-size: (450, 700)
192assert-css: ("nav.sub", {"flex-direction": "column"})
193
194171// Check the sidebar directory entries have a marker and spacing (phone).
172set-window-size: (450, 700)
195173store-property: (".src-sidebar-title", {
196174 "offsetHeight": source_sidebar_title_height,
197175 "offsetTop": source_sidebar_title_y,
tests/rustdoc-gui/source-code-wrapping.goml+2-2
......@@ -13,7 +13,7 @@ define-function: (
1313)
1414
1515store-size: (".rust code", {"width": width, "height": height})
16click: "#settings-menu"
16click: "main .settings-menu"
1717wait-for: "#settings"
1818call-function: ("click-code-wrapping", {"expected": "true"})
1919wait-for-size-false: (".rust code", {"width": |width|, "height": |height|})
......@@ -28,7 +28,7 @@ assert-size: (".rust code", {"width": |width|, "height": |height|})
2828
2929// Now let's check in docs code examples.
3030go-to: "file://" + |DOC_PATH| + "/test_docs/trait_bounds/index.html"
31click: "#settings-menu"
31click: "main .settings-menu"
3232wait-for: "#settings"
3333
3434store-property: (".example-wrap .rust code", {"scrollWidth": rust_width, "scrollHeight": rust_height})
tests/rustdoc-gui/theme-change.goml+2-2
......@@ -7,7 +7,7 @@ store-value: (background_light, "white")
77store-value: (background_dark, "#353535")
88store-value: (background_ayu, "#0f1419")
99
10click: "#settings-menu"
10click: "rustdoc-toolbar .settings-menu"
1111wait-for: "#theme-ayu"
1212click: "#theme-ayu"
1313// should be the ayu theme so let's check the color.
......@@ -75,7 +75,7 @@ store-value: (background_dark, "#353535")
7575store-value: (background_ayu, "#0f1419")
7676store-value: (background_custom_theme, "red")
7777
78click: "#settings-menu"
78click: "rustdoc-toolbar .settings-menu"
7979wait-for: "#theme-ayu"
8080click: "#theme-ayu"
8181// should be the ayu theme so let's check the color.
tests/rustdoc-gui/theme-defaults.goml+2-2
......@@ -1,6 +1,6 @@
11// Ensure that the theme picker always starts with the actual defaults.
22go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
3click: "#settings-menu"
3click: "rustdoc-toolbar .settings-menu"
44wait-for: "#theme-system-preference"
55assert: "#theme-system-preference:checked"
66assert: "#preferred-light-theme-light:checked"
......@@ -16,7 +16,7 @@ set-local-storage: {
1616 "rustdoc-theme": "ayu"
1717}
1818go-to: "file://" + |DOC_PATH| + "/test_docs/index.html"
19click: "#settings-menu"
19click: "rustdoc-toolbar .settings-menu"
2020wait-for: "#theme-system-preference"
2121assert: "#theme-system-preference:checked"
2222assert: "#preferred-light-theme-light:checked"
tests/rustdoc-gui/toggle-click-deadspace.goml+1-1
......@@ -13,4 +13,4 @@ assert-attribute-false: (".impl-items .toggle", {"open": ""})
1313// Click the "Trait" part of "impl Trait" and verify it navigates.
1414click: "#impl-Trait-for-Foo h3 a:first-of-type"
1515assert-text: (".main-heading .rustdoc-breadcrumbs", "lib2")
16assert-text: (".main-heading h1", "Trait TraitCopy item path")
16assert-text: (".main-heading h1", "Trait Trait Copy item path")
tests/rustdoc-gui/toggle-docs-mobile.goml+6-6
......@@ -3,12 +3,12 @@
33go-to: "file://" + |DOC_PATH| + "/test_docs/struct.Foo.html"
44set-window-size: (433, 600)
55assert-attribute: (".top-doc", {"open": ""})
6click: (4, 270) // This is the position of the top doc comment toggle
6click: (4, 230) // This is the position of the top doc comment toggle
77assert-attribute-false: (".top-doc", {"open": ""})
8click: (4, 270)
8click: (4, 230)
99assert-attribute: (".top-doc", {"open": ""})
1010// To ensure that the toggle isn't over the text, we check that the toggle isn't clicked.
11click: (3, 270)
11click: (3, 230)
1212assert-attribute: (".top-doc", {"open": ""})
1313
1414// Assert the position of the toggle on the top doc block.
......@@ -24,12 +24,12 @@ assert-position: (
2424// Now we do the same but with a little bigger width
2525set-window-size: (600, 600)
2626assert-attribute: (".top-doc", {"open": ""})
27click: (4, 270) // New Y position since all search elements are back on one line.
27click: (4, 230) // New Y position since all search elements are back on one line.
2828assert-attribute-false: (".top-doc", {"open": ""})
29click: (4, 270)
29click: (4, 230)
3030assert-attribute: (".top-doc", {"open": ""})
3131// To ensure that the toggle isn't over the text, we check that the toggle isn't clicked.
32click: (3, 270)
32click: (3, 230)
3333assert-attribute: (".top-doc", {"open": ""})
3434
3535// Same check on trait items.
tests/rustdoc-gui/toggle-docs.goml+1-1
......@@ -64,7 +64,7 @@ define-function: (
6464 "filter": |filter|,
6565 })
6666 // moving the cursor somewhere else to not mess with next function calls.
67 move-cursor-to: ".search-input"
67 move-cursor-to: "#search-button"
6868 },
6969)
7070
tests/rustdoc-gui/type-declation-overflow.goml+11-11
......@@ -47,27 +47,27 @@ assert-property: ("pre.item-decl", {"scrollWidth": "950"})
4747set-window-size: (600, 600)
4848go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
4949// It shouldn't have an overflow in the topbar either.
50store-property: (".mobile-topbar", {"scrollWidth": scrollWidth})
51assert-property: (".mobile-topbar", {"clientWidth": |scrollWidth|})
52assert-css: (".mobile-topbar h2", {"overflow-x": "hidden"})
50store-property: ("rustdoc-topbar", {"scrollWidth": scrollWidth})
51assert-property: ("rustdoc-topbar", {"clientWidth": |scrollWidth|}, NEAR)
52assert-css: ("rustdoc-topbar h2", {"overflow-x": "hidden"})
5353
5454// Check that main heading and toolbar go side-by-side, both on desktop and on mobile.
5555set-window-size: (1100, 800)
5656go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
57compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
58compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 550})
57compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
58compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 300})
5959go-to: "file://" + |DOC_PATH| + "/lib2/index.html"
60compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
61compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 550})
60compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
61compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 300})
6262
6363// On mobile, they always wrap.
6464set-window-size: (600, 600)
6565go-to: "file://" + |DOC_PATH| + "/lib2/too_long/struct.SuperIncrediblyLongLongLongLongLongLongLongGigaGigaGigaMegaLongLongLongStructName.html"
66compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
67compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 200})
66compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
67compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 200})
6868go-to: "file://" + |DOC_PATH| + "/lib2/index.html"
69compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar", ["y"])
70compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar", {"x": 200})
69compare-elements-position: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", ["y"])
70compare-elements-position-near-false: (".main-heading h1", ".main-heading rustdoc-toolbar #search-button", {"x": 200})
7171
7272// Now we will check that the scrolling is working.
7373// First on an item with "hidden methods".
tests/rustdoc-gui/utils.goml+35-2
......@@ -5,14 +5,47 @@ define-function: (
55 block {
66 // Set the theme.
77 // Open the settings menu.
8 click: "#settings-menu"
8 click: "rustdoc-toolbar .settings-menu"
99 // Wait for the popover to appear...
1010 wait-for: "#settings"
1111 // Change the setting.
1212 click: "#theme-"+ |theme|
1313 // Close the popover.
14 click: "#settings-menu"
14 click: "rustdoc-toolbar .settings-menu"
1515 // Ensure that the local storage was correctly updated.
1616 assert-local-storage: {"rustdoc-theme": |theme|}
1717 },
1818)
19
20define-function: (
21 "switch-theme-mobile",
22 [theme],
23 block {
24 // Set the theme.
25 // Open the settings menu.
26 click: "rustdoc-topbar .settings-menu"
27 // Wait for the popover to appear...
28 wait-for: "#settings"
29 // Change the setting.
30 click: "#theme-"+ |theme|
31 // Close the popover.
32 click: "rustdoc-topbar .settings-menu"
33 // Ensure that the local storage was correctly updated.
34 assert-local-storage: {"rustdoc-theme": |theme|}
35 },
36)
37
38define-function: (
39 "perform-search",
40 [query],
41 block {
42 click: "#search-button"
43 wait-for: ".search-input"
44 write-into: (".search-input", |query|)
45 press-key: 'Enter'
46 // wait for the search to start
47 wait-for: "#search-tabs"
48 // then wait for it to finish
49 wait-for-false: "#search-tabs .count.loading"
50 }
51)
tests/rustdoc-js-std/alias-1.js+5
......@@ -6,5 +6,10 @@ const EXPECTED = {
66 'name': 'reference',
77 'desc': "References, <code>&amp;T</code> and <code>&amp;mut T</code>.",
88 },
9 {
10 'path': 'std::ops',
11 'name': 'BitAnd',
12 'desc': "The bitwise AND operator <code>&amp;</code>.",
13 },
914 ],
1015};
tests/rustdoc-js-std/alias-2.js+1-3
......@@ -1,9 +1,7 @@
11const EXPECTED = {
22 'query': '+',
33 'others': [
4 { 'path': 'std::ops', 'name': 'AddAssign' },
54 { 'path': 'std::ops', 'name': 'Add' },
6 { 'path': 'core::ops', 'name': 'AddAssign' },
7 { 'path': 'core::ops', 'name': 'Add' },
5 { 'path': 'std::ops', 'name': 'AddAssign' },
86 ],
97};
tests/rustdoc-js-std/basic.js+1-1
......@@ -9,6 +9,6 @@ const EXPECTED = {
99 { 'path': 'std::str', 'name': 'eq' },
1010 ],
1111 'returned': [
12 { 'path': 'std::string::String', 'name': 'add' },
12 { 'path': 'std::string::String', 'name': 'new' },
1313 ],
1414};
tests/rustdoc-js-std/parser-bindings.js+14-14
......@@ -20,12 +20,12 @@ const PARSED = [
2020 pathLast: "c",
2121 normalizedPathLast: "c",
2222 generics: [],
23 typeFilter: -1,
23 typeFilter: null,
2424 },
2525 ]
2626 ],
2727 ],
28 typeFilter: -1,
28 typeFilter: null,
2929 },
3030 ],
3131 foundElems: 1,
......@@ -51,11 +51,11 @@ const PARSED = [
5151 pathWithoutLast: [],
5252 pathLast: "c",
5353 generics: [],
54 typeFilter: -1,
54 typeFilter: null,
5555 }]
5656 ],
5757 ],
58 typeFilter: -1,
58 typeFilter: null,
5959 },
6060 ],
6161 foundElems: 1,
......@@ -81,11 +81,11 @@ const PARSED = [
8181 pathWithoutLast: [],
8282 pathLast: "never",
8383 generics: [],
84 typeFilter: 1,
84 typeFilter: "primitive",
8585 }]
8686 ],
8787 ],
88 typeFilter: -1,
88 typeFilter: null,
8989 },
9090 ],
9191 foundElems: 1,
......@@ -111,11 +111,11 @@ const PARSED = [
111111 pathWithoutLast: [],
112112 pathLast: "[]",
113113 generics: [],
114 typeFilter: 1,
114 typeFilter: "primitive",
115115 }]
116116 ],
117117 ],
118 typeFilter: -1,
118 typeFilter: null,
119119 },
120120 ],
121121 foundElems: 1,
......@@ -147,14 +147,14 @@ const PARSED = [
147147 pathWithoutLast: [],
148148 pathLast: "never",
149149 generics: [],
150 typeFilter: 1,
150 typeFilter: "primitive",
151151 },
152152 ],
153 typeFilter: 1,
153 typeFilter: "primitive",
154154 }]
155155 ],
156156 ],
157 typeFilter: -1,
157 typeFilter: null,
158158 },
159159 ],
160160 foundElems: 1,
......@@ -213,7 +213,7 @@ const PARSED = [
213213 pathWithoutLast: [],
214214 pathLast: "c",
215215 generics: [],
216 typeFilter: -1,
216 typeFilter: null,
217217 },
218218 {
219219 name: "X",
......@@ -221,12 +221,12 @@ const PARSED = [
221221 pathWithoutLast: [],
222222 pathLast: "x",
223223 generics: [],
224 typeFilter: -1,
224 typeFilter: null,
225225 },
226226 ],
227227 ],
228228 ],
229 typeFilter: -1,
229 typeFilter: null,
230230 },
231231 ],
232232 foundElems: 1,
tests/rustdoc-js-std/parser-errors.js+9-9
......@@ -406,10 +406,10 @@ const PARSED = [
406406 pathWithoutLast: [],
407407 pathLast: "x",
408408 generics: [],
409 typeFilter: -1,
409 typeFilter: null,
410410 },
411411 ],
412 typeFilter: -1,
412 typeFilter: null,
413413 },
414414 {
415415 name: "y",
......@@ -417,7 +417,7 @@ const PARSED = [
417417 pathWithoutLast: [],
418418 pathLast: "y",
419419 generics: [],
420 typeFilter: -1,
420 typeFilter: null,
421421 },
422422 ],
423423 foundElems: 2,
......@@ -440,7 +440,7 @@ const PARSED = [
440440 pathWithoutLast: [],
441441 pathLast: "x",
442442 generics: [],
443 typeFilter: -1,
443 typeFilter: null,
444444 },
445445 {
446446 name: "y",
......@@ -448,10 +448,10 @@ const PARSED = [
448448 pathWithoutLast: [],
449449 pathLast: "y",
450450 generics: [],
451 typeFilter: -1,
451 typeFilter: null,
452452 },
453453 ],
454 typeFilter: -1,
454 typeFilter: null,
455455 },
456456 ],
457457 foundElems: 1,
......@@ -468,7 +468,7 @@ const PARSED = [
468468 pathWithoutLast: [],
469469 pathLast: "p",
470470 generics: [],
471 typeFilter: -1,
471 typeFilter: null,
472472 },
473473 {
474474 name: "x",
......@@ -476,7 +476,7 @@ const PARSED = [
476476 pathWithoutLast: [],
477477 pathLast: "x",
478478 generics: [],
479 typeFilter: -1,
479 typeFilter: null,
480480 },
481481 {
482482 name: "y",
......@@ -484,7 +484,7 @@ const PARSED = [
484484 pathWithoutLast: [],
485485 pathLast: "y",
486486 generics: [],
487 typeFilter: -1,
487 typeFilter: null,
488488 },
489489 ],
490490 foundElems: 3,
tests/rustdoc-js-std/parser-filter.js+11-11
......@@ -7,7 +7,7 @@ const PARSED = [
77 pathWithoutLast: [],
88 pathLast: "foo",
99 generics: [],
10 typeFilter: 7,
10 typeFilter: "fn",
1111 }],
1212 foundElems: 1,
1313 userQuery: "fn:foo",
......@@ -22,7 +22,7 @@ const PARSED = [
2222 pathWithoutLast: [],
2323 pathLast: "foo",
2424 generics: [],
25 typeFilter: 6,
25 typeFilter: "enum",
2626 }],
2727 foundElems: 1,
2828 userQuery: "enum : foo",
......@@ -45,7 +45,7 @@ const PARSED = [
4545 pathWithoutLast: [],
4646 pathLast: "macro",
4747 generics: [],
48 typeFilter: 16,
48 typeFilter: "macro",
4949 }],
5050 foundElems: 1,
5151 userQuery: "macro!",
......@@ -60,7 +60,7 @@ const PARSED = [
6060 pathWithoutLast: [],
6161 pathLast: "mac",
6262 generics: [],
63 typeFilter: 16,
63 typeFilter: "macro",
6464 }],
6565 foundElems: 1,
6666 userQuery: "macro:mac!",
......@@ -75,7 +75,7 @@ const PARSED = [
7575 pathWithoutLast: ["a"],
7676 pathLast: "mac",
7777 generics: [],
78 typeFilter: 16,
78 typeFilter: "macro",
7979 }],
8080 foundElems: 1,
8181 userQuery: "a::mac!",
......@@ -93,7 +93,7 @@ const PARSED = [
9393 pathWithoutLast: [],
9494 pathLast: "foo",
9595 generics: [],
96 typeFilter: 7,
96 typeFilter: "fn",
9797 }],
9898 error: null,
9999 },
......@@ -114,10 +114,10 @@ const PARSED = [
114114 pathWithoutLast: [],
115115 pathLast: "bar",
116116 generics: [],
117 typeFilter: 7,
117 typeFilter: "fn",
118118 }
119119 ],
120 typeFilter: 7,
120 typeFilter: "fn",
121121 }],
122122 error: null,
123123 },
......@@ -138,7 +138,7 @@ const PARSED = [
138138 pathWithoutLast: [],
139139 pathLast: "bar",
140140 generics: [],
141 typeFilter: 7,
141 typeFilter: "fn",
142142 },
143143 {
144144 name: "baz::fuzz",
......@@ -146,10 +146,10 @@ const PARSED = [
146146 pathWithoutLast: ["baz"],
147147 pathLast: "fuzz",
148148 generics: [],
149 typeFilter: 6,
149 typeFilter: "enum",
150150 },
151151 ],
152 typeFilter: 7,
152 typeFilter: "fn",
153153 }],
154154 error: null,
155155 },
tests/rustdoc-js-std/parser-generics.js+6-6
......@@ -16,7 +16,7 @@ const PARSED = [
1616 pathWithoutLast: [],
1717 pathLast: "p",
1818 generics: [],
19 typeFilter: -1,
19 typeFilter: null,
2020 },
2121 {
2222 name: "u8",
......@@ -24,7 +24,7 @@ const PARSED = [
2424 pathWithoutLast: [],
2525 pathLast: "u8",
2626 generics: [],
27 typeFilter: -1,
27 typeFilter: null,
2828 },
2929 ],
3030 foundElems: 2,
......@@ -49,7 +49,7 @@ const PARSED = [
4949 generics: [],
5050 },
5151 ],
52 typeFilter: -1,
52 typeFilter: null,
5353 },
5454 ],
5555 foundElems: 1,
......@@ -82,7 +82,7 @@ const PARSED = [
8282 ],
8383 },
8484 ],
85 typeFilter: -1,
85 typeFilter: null,
8686 },
8787 ],
8888 foundElems: 1,
......@@ -122,7 +122,7 @@ const PARSED = [
122122 generics: [],
123123 },
124124 ],
125 typeFilter: -1,
125 typeFilter: null,
126126 },
127127 ],
128128 foundElems: 1,
......@@ -162,7 +162,7 @@ const PARSED = [
162162 ],
163163 },
164164 ],
165 typeFilter: -1,
165 typeFilter: null,
166166 },
167167 ],
168168 foundElems: 1,
tests/rustdoc-js-std/parser-hof.js+53-53
......@@ -25,11 +25,11 @@ const PARSED = [
2525 generics: [],
2626 },
2727 ],
28 typeFilter: -1,
28 typeFilter: null,
2929 }],
3030 ],
3131 ],
32 typeFilter: -1,
32 typeFilter: null,
3333 }],
3434 foundElems: 1,
3535 userQuery: "(-> F<P>)",
......@@ -53,11 +53,11 @@ const PARSED = [
5353 pathWithoutLast: [],
5454 pathLast: "p",
5555 generics: [],
56 typeFilter: -1,
56 typeFilter: null,
5757 }],
5858 ],
5959 ],
60 typeFilter: -1,
60 typeFilter: null,
6161 }],
6262 foundElems: 1,
6363 userQuery: "(-> P)",
......@@ -81,11 +81,11 @@ const PARSED = [
8181 pathWithoutLast: [],
8282 pathLast: "a",
8383 generics: [],
84 typeFilter: -1,
84 typeFilter: null,
8585 }],
8686 ],
8787 ],
88 typeFilter: -1,
88 typeFilter: null,
8989 }],
9090 foundElems: 1,
9191 userQuery: "(->,a)",
......@@ -113,7 +113,7 @@ const PARSED = [
113113 generics: [],
114114 },
115115 ],
116 typeFilter: -1,
116 typeFilter: null,
117117 }],
118118 bindings: [
119119 [
......@@ -121,7 +121,7 @@ const PARSED = [
121121 [],
122122 ],
123123 ],
124 typeFilter: -1,
124 typeFilter: null,
125125 }],
126126 foundElems: 1,
127127 userQuery: "(F<P> ->)",
......@@ -141,7 +141,7 @@ const PARSED = [
141141 pathWithoutLast: [],
142142 pathLast: "p",
143143 generics: [],
144 typeFilter: -1,
144 typeFilter: null,
145145 }],
146146 bindings: [
147147 [
......@@ -149,7 +149,7 @@ const PARSED = [
149149 [],
150150 ],
151151 ],
152 typeFilter: -1,
152 typeFilter: null,
153153 }],
154154 foundElems: 1,
155155 userQuery: "(P ->)",
......@@ -169,7 +169,7 @@ const PARSED = [
169169 pathWithoutLast: [],
170170 pathLast: "a",
171171 generics: [],
172 typeFilter: -1,
172 typeFilter: null,
173173 }],
174174 bindings: [
175175 [
......@@ -177,7 +177,7 @@ const PARSED = [
177177 [],
178178 ],
179179 ],
180 typeFilter: -1,
180 typeFilter: null,
181181 }],
182182 foundElems: 1,
183183 userQuery: "(,a->)",
......@@ -197,7 +197,7 @@ const PARSED = [
197197 pathWithoutLast: [],
198198 pathLast: "aaaaa",
199199 generics: [],
200 typeFilter: -1,
200 typeFilter: null,
201201 }],
202202 bindings: [
203203 [
......@@ -208,11 +208,11 @@ const PARSED = [
208208 pathWithoutLast: [],
209209 pathLast: "a",
210210 generics: [],
211 typeFilter: -1,
211 typeFilter: null,
212212 }],
213213 ],
214214 ],
215 typeFilter: -1,
215 typeFilter: null,
216216 }],
217217 foundElems: 1,
218218 userQuery: "(aaaaa->a)",
......@@ -233,7 +233,7 @@ const PARSED = [
233233 pathWithoutLast: [],
234234 pathLast: "aaaaa",
235235 generics: [],
236 typeFilter: -1,
236 typeFilter: null,
237237 },
238238 {
239239 name: "b",
......@@ -241,7 +241,7 @@ const PARSED = [
241241 pathWithoutLast: [],
242242 pathLast: "b",
243243 generics: [],
244 typeFilter: -1,
244 typeFilter: null,
245245 },
246246 ],
247247 bindings: [
......@@ -253,11 +253,11 @@ const PARSED = [
253253 pathWithoutLast: [],
254254 pathLast: "a",
255255 generics: [],
256 typeFilter: -1,
256 typeFilter: null,
257257 }],
258258 ],
259259 ],
260 typeFilter: -1,
260 typeFilter: null,
261261 }],
262262 foundElems: 1,
263263 userQuery: "(aaaaa, b -> a)",
......@@ -278,7 +278,7 @@ const PARSED = [
278278 pathWithoutLast: [],
279279 pathLast: "aaaaa",
280280 generics: [],
281 typeFilter: -1,
281 typeFilter: null,
282282 },
283283 {
284284 name: "b",
......@@ -286,7 +286,7 @@ const PARSED = [
286286 pathWithoutLast: [],
287287 pathLast: "b",
288288 generics: [],
289 typeFilter: -1,
289 typeFilter: null,
290290 },
291291 ],
292292 bindings: [
......@@ -298,11 +298,11 @@ const PARSED = [
298298 pathWithoutLast: [],
299299 pathLast: "a",
300300 generics: [],
301 typeFilter: -1,
301 typeFilter: null,
302302 }],
303303 ],
304304 ],
305 typeFilter: 1,
305 typeFilter: "primitive",
306306 }],
307307 foundElems: 1,
308308 userQuery: "primitive:(aaaaa, b -> a)",
......@@ -318,7 +318,7 @@ const PARSED = [
318318 pathWithoutLast: [],
319319 pathLast: "x",
320320 generics: [],
321 typeFilter: -1,
321 typeFilter: null,
322322 },
323323 {
324324 name: "->",
......@@ -332,7 +332,7 @@ const PARSED = [
332332 pathWithoutLast: [],
333333 pathLast: "aaaaa",
334334 generics: [],
335 typeFilter: -1,
335 typeFilter: null,
336336 },
337337 {
338338 name: "b",
......@@ -340,7 +340,7 @@ const PARSED = [
340340 pathWithoutLast: [],
341341 pathLast: "b",
342342 generics: [],
343 typeFilter: -1,
343 typeFilter: null,
344344 },
345345 ],
346346 bindings: [
......@@ -352,11 +352,11 @@ const PARSED = [
352352 pathWithoutLast: [],
353353 pathLast: "a",
354354 generics: [],
355 typeFilter: -1,
355 typeFilter: null,
356356 }],
357357 ],
358358 ],
359 typeFilter: 10,
359 typeFilter: "trait",
360360 }
361361 ],
362362 foundElems: 2,
......@@ -390,11 +390,11 @@ const PARSED = [
390390 generics: [],
391391 },
392392 ],
393 typeFilter: -1,
393 typeFilter: null,
394394 }],
395395 ],
396396 ],
397 typeFilter: -1,
397 typeFilter: null,
398398 }],
399399 foundElems: 1,
400400 userQuery: "Fn () -> F<P>",
......@@ -418,11 +418,11 @@ const PARSED = [
418418 pathWithoutLast: [],
419419 pathLast: "p",
420420 generics: [],
421 typeFilter: -1,
421 typeFilter: null,
422422 }],
423423 ],
424424 ],
425 typeFilter: -1,
425 typeFilter: null,
426426 }],
427427 foundElems: 1,
428428 userQuery: "FnMut() -> P",
......@@ -446,11 +446,11 @@ const PARSED = [
446446 pathWithoutLast: [],
447447 pathLast: "p",
448448 generics: [],
449 typeFilter: -1,
449 typeFilter: null,
450450 }],
451451 ],
452452 ],
453 typeFilter: -1,
453 typeFilter: null,
454454 }],
455455 foundElems: 1,
456456 userQuery: "(FnMut() -> P)",
......@@ -478,7 +478,7 @@ const PARSED = [
478478 generics: [],
479479 },
480480 ],
481 typeFilter: -1,
481 typeFilter: null,
482482 }],
483483 bindings: [
484484 [
......@@ -486,7 +486,7 @@ const PARSED = [
486486 [],
487487 ],
488488 ],
489 typeFilter: -1,
489 typeFilter: null,
490490 }],
491491 foundElems: 1,
492492 userQuery: "Fn(F<P>)",
......@@ -507,7 +507,7 @@ const PARSED = [
507507 pathWithoutLast: [],
508508 pathLast: "aaaaa",
509509 generics: [],
510 typeFilter: -1,
510 typeFilter: null,
511511 },
512512 {
513513 name: "b",
......@@ -515,7 +515,7 @@ const PARSED = [
515515 pathWithoutLast: [],
516516 pathLast: "b",
517517 generics: [],
518 typeFilter: -1,
518 typeFilter: null,
519519 },
520520 ],
521521 bindings: [
......@@ -527,11 +527,11 @@ const PARSED = [
527527 pathWithoutLast: [],
528528 pathLast: "a",
529529 generics: [],
530 typeFilter: -1,
530 typeFilter: null,
531531 }],
532532 ],
533533 ],
534 typeFilter: 1,
534 typeFilter: "primitive",
535535 }],
536536 foundElems: 1,
537537 userQuery: "primitive:fnonce(aaaaa, b) -> a",
......@@ -552,7 +552,7 @@ const PARSED = [
552552 pathWithoutLast: [],
553553 pathLast: "aaaaa",
554554 generics: [],
555 typeFilter: -1,
555 typeFilter: null,
556556 },
557557 {
558558 name: "b",
......@@ -560,7 +560,7 @@ const PARSED = [
560560 pathWithoutLast: [],
561561 pathLast: "b",
562562 generics: [],
563 typeFilter: 0,
563 typeFilter: "keyword",
564564 },
565565 ],
566566 bindings: [
......@@ -572,11 +572,11 @@ const PARSED = [
572572 pathWithoutLast: [],
573573 pathLast: "a",
574574 generics: [],
575 typeFilter: 10,
575 typeFilter: "trait",
576576 }],
577577 ],
578578 ],
579 typeFilter: 1,
579 typeFilter: "primitive",
580580 }],
581581 foundElems: 1,
582582 userQuery: "primitive:fnonce(aaaaa, keyword:b) -> trait:a",
......@@ -592,7 +592,7 @@ const PARSED = [
592592 pathWithoutLast: [],
593593 pathLast: "x",
594594 generics: [],
595 typeFilter: -1,
595 typeFilter: null,
596596 },
597597 {
598598 name: "fn",
......@@ -612,7 +612,7 @@ const PARSED = [
612612 pathWithoutLast: [],
613613 pathLast: "aaaaa",
614614 generics: [],
615 typeFilter: -1,
615 typeFilter: null,
616616 },
617617 {
618618 name: "b",
......@@ -620,7 +620,7 @@ const PARSED = [
620620 pathWithoutLast: [],
621621 pathLast: "b",
622622 generics: [],
623 typeFilter: -1,
623 typeFilter: null,
624624 },
625625 ],
626626 bindings: [
......@@ -632,11 +632,11 @@ const PARSED = [
632632 pathWithoutLast: [],
633633 pathLast: "a",
634634 generics: [],
635 typeFilter: -1,
635 typeFilter: null,
636636 }],
637637 ],
638638 ],
639 typeFilter: -1,
639 typeFilter: null,
640640 },
641641 ],
642642 bindings: [
......@@ -645,7 +645,7 @@ const PARSED = [
645645 [],
646646 ]
647647 ],
648 typeFilter: 10,
648 typeFilter: "trait",
649649 }
650650 ],
651651 foundElems: 2,
......@@ -662,7 +662,7 @@ const PARSED = [
662662 pathWithoutLast: [],
663663 pathLast: "a",
664664 generics: [],
665 typeFilter: -1,
665 typeFilter: null,
666666 },
667667 {
668668 name: "b",
......@@ -675,7 +675,7 @@ const PARSED = [
675675 pathWithoutLast: [],
676676 pathLast: "c",
677677 generics: [],
678 typeFilter: -1,
678 typeFilter: null,
679679 }],
680680 bindings: [
681681 [
......@@ -683,7 +683,7 @@ const PARSED = [
683683 [],
684684 ]
685685 ],
686 typeFilter: -1,
686 typeFilter: null,
687687 }
688688 ],
689689 foundElems: 2,
tests/rustdoc-js-std/parser-ident.js+7-7
......@@ -13,10 +13,10 @@ const PARSED = [
1313 pathWithoutLast: [],
1414 pathLast: "never",
1515 generics: [],
16 typeFilter: 1,
16 typeFilter: "primitive",
1717 },
1818 ],
19 typeFilter: -1,
19 typeFilter: null,
2020 }],
2121 foundElems: 1,
2222 userQuery: "R<!>",
......@@ -31,7 +31,7 @@ const PARSED = [
3131 pathWithoutLast: [],
3232 pathLast: "never",
3333 generics: [],
34 typeFilter: 1,
34 typeFilter: "primitive",
3535 }],
3636 foundElems: 1,
3737 userQuery: "!",
......@@ -46,7 +46,7 @@ const PARSED = [
4646 pathWithoutLast: [],
4747 pathLast: "a",
4848 generics: [],
49 typeFilter: 16,
49 typeFilter: "macro",
5050 }],
5151 foundElems: 1,
5252 userQuery: "a!",
......@@ -77,7 +77,7 @@ const PARSED = [
7777 pathWithoutLast: ["never"],
7878 pathLast: "b",
7979 generics: [],
80 typeFilter: -1,
80 typeFilter: null,
8181 }],
8282 foundElems: 1,
8383 userQuery: "!::b",
......@@ -122,10 +122,10 @@ const PARSED = [
122122 pathWithoutLast: [],
123123 pathLast: "t",
124124 generics: [],
125 typeFilter: -1,
125 typeFilter: null,
126126 }
127127 ],
128 typeFilter: -1,
128 typeFilter: null,
129129 }],
130130 foundElems: 1,
131131 userQuery: "!::b<T>",
tests/rustdoc-js-std/parser-literal.js+1-1
......@@ -15,7 +15,7 @@ const PARSED = [
1515 generics: [],
1616 },
1717 ],
18 typeFilter: -1,
18 typeFilter: null,
1919 }],
2020 foundElems: 1,
2121 userQuery: "R<P>",
tests/rustdoc-js-std/parser-paths.js+9-9
......@@ -7,7 +7,7 @@ const PARSED = [
77 pathWithoutLast: ["a"],
88 pathLast: "b",
99 generics: [],
10 typeFilter: -1,
10 typeFilter: null,
1111 }],
1212 foundElems: 1,
1313 userQuery: "A::B",
......@@ -22,7 +22,7 @@ const PARSED = [
2222 pathWithoutLast: ["a"],
2323 pathLast: "a",
2424 generics: [],
25 typeFilter: -1,
25 typeFilter: null,
2626 }],
2727 foundElems: 1,
2828 userQuery: 'a:: a',
......@@ -37,7 +37,7 @@ const PARSED = [
3737 pathWithoutLast: ["a"],
3838 pathLast: "a",
3939 generics: [],
40 typeFilter: -1,
40 typeFilter: null,
4141 }],
4242 foundElems: 1,
4343 userQuery: 'a ::a',
......@@ -52,7 +52,7 @@ const PARSED = [
5252 pathWithoutLast: ["a"],
5353 pathLast: "a",
5454 generics: [],
55 typeFilter: -1,
55 typeFilter: null,
5656 }],
5757 foundElems: 1,
5858 userQuery: 'a :: a',
......@@ -68,7 +68,7 @@ const PARSED = [
6868 pathWithoutLast: ["a"],
6969 pathLast: "b",
7070 generics: [],
71 typeFilter: -1,
71 typeFilter: null,
7272 },
7373 {
7474 name: "C",
......@@ -76,7 +76,7 @@ const PARSED = [
7676 pathWithoutLast: [],
7777 pathLast: "c",
7878 generics: [],
79 typeFilter: -1,
79 typeFilter: null,
8080 },
8181 ],
8282 foundElems: 2,
......@@ -101,7 +101,7 @@ const PARSED = [
101101 generics: [],
102102 },
103103 ],
104 typeFilter: -1,
104 typeFilter: null,
105105 },
106106 {
107107 name: "C",
......@@ -109,7 +109,7 @@ const PARSED = [
109109 pathWithoutLast: [],
110110 pathLast: "c",
111111 generics: [],
112 typeFilter: -1,
112 typeFilter: null,
113113 },
114114 ],
115115 foundElems: 2,
......@@ -125,7 +125,7 @@ const PARSED = [
125125 pathWithoutLast: ["mod"],
126126 pathLast: "a",
127127 generics: [],
128 typeFilter: -1,
128 typeFilter: null,
129129 }],
130130 foundElems: 1,
131131 userQuery: "mod::a",
tests/rustdoc-js-std/parser-quote.js+2-2
......@@ -10,7 +10,7 @@ const PARSED = [
1010 pathWithoutLast: [],
1111 pathLast: "p",
1212 generics: [],
13 typeFilter: -1,
13 typeFilter: null,
1414 }],
1515 error: null,
1616 },
......@@ -22,7 +22,7 @@ const PARSED = [
2222 pathWithoutLast: [],
2323 pathLast: "p",
2424 generics: [],
25 typeFilter: -1,
25 typeFilter: null,
2626 }],
2727 foundElems: 1,
2828 userQuery: '"p",',
tests/rustdoc-js-std/parser-reference.js+42-42
......@@ -42,16 +42,16 @@ const PARSED = [
4242 pathWithoutLast: [],
4343 pathLast: "d",
4444 generics: [],
45 typeFilter: -1,
45 typeFilter: null,
4646 },
4747 ],
48 typeFilter: 1,
48 typeFilter: "primitive",
4949 },
5050 ],
51 typeFilter: 1,
51 typeFilter: "primitive",
5252 },
5353 ],
54 typeFilter: 1,
54 typeFilter: "primitive",
5555 },
5656 {
5757 name: "[]",
......@@ -59,7 +59,7 @@ const PARSED = [
5959 pathWithoutLast: [],
6060 pathLast: "[]",
6161 generics: [],
62 typeFilter: 1,
62 typeFilter: "primitive",
6363 },
6464 ],
6565 foundElems: 2,
......@@ -100,19 +100,19 @@ const PARSED = [
100100 pathWithoutLast: [],
101101 pathLast: "d",
102102 generics: [],
103 typeFilter: -1,
103 typeFilter: null,
104104 },
105105 ],
106 typeFilter: 1,
106 typeFilter: "primitive",
107107 },
108108 ],
109 typeFilter: 1,
109 typeFilter: "primitive",
110110 },
111111 ],
112 typeFilter: 1,
112 typeFilter: "primitive",
113113 },
114114 ],
115 typeFilter: 1,
115 typeFilter: "primitive",
116116 },
117117 ],
118118 foundElems: 1,
......@@ -129,7 +129,7 @@ const PARSED = [
129129 pathWithoutLast: [],
130130 pathLast: "reference",
131131 generics: [],
132 typeFilter: 1,
132 typeFilter: "primitive",
133133 },
134134 ],
135135 foundElems: 1,
......@@ -152,10 +152,10 @@ const PARSED = [
152152 pathWithoutLast: [],
153153 pathLast: "mut",
154154 generics: [],
155 typeFilter: 0,
155 typeFilter: "keyword",
156156 },
157157 ],
158 typeFilter: 1,
158 typeFilter: "primitive",
159159 },
160160 ],
161161 foundElems: 1,
......@@ -172,7 +172,7 @@ const PARSED = [
172172 pathWithoutLast: [],
173173 pathLast: "reference",
174174 generics: [],
175 typeFilter: 1,
175 typeFilter: "primitive",
176176 },
177177 {
178178 name: "u8",
......@@ -180,7 +180,7 @@ const PARSED = [
180180 pathWithoutLast: [],
181181 pathLast: "u8",
182182 generics: [],
183 typeFilter: -1,
183 typeFilter: null,
184184 },
185185 ],
186186 foundElems: 2,
......@@ -203,10 +203,10 @@ const PARSED = [
203203 pathWithoutLast: [],
204204 pathLast: "mut",
205205 generics: [],
206 typeFilter: 0,
206 typeFilter: "keyword",
207207 },
208208 ],
209 typeFilter: 1,
209 typeFilter: "primitive",
210210 },
211211 {
212212 name: "u8",
......@@ -214,7 +214,7 @@ const PARSED = [
214214 pathWithoutLast: [],
215215 pathLast: "u8",
216216 generics: [],
217 typeFilter: -1,
217 typeFilter: null,
218218 },
219219 ],
220220 foundElems: 2,
......@@ -237,10 +237,10 @@ const PARSED = [
237237 pathWithoutLast: [],
238238 pathLast: "u8",
239239 generics: [],
240 typeFilter: -1,
240 typeFilter: null,
241241 },
242242 ],
243 typeFilter: 1,
243 typeFilter: "primitive",
244244 },
245245 ],
246246 foundElems: 1,
......@@ -269,13 +269,13 @@ const PARSED = [
269269 pathWithoutLast: [],
270270 pathLast: "u8",
271271 generics: [],
272 typeFilter: -1,
272 typeFilter: null,
273273 },
274274 ],
275 typeFilter: -1,
275 typeFilter: null,
276276 },
277277 ],
278 typeFilter: 1,
278 typeFilter: "primitive",
279279 },
280280 ],
281281 foundElems: 1,
......@@ -304,13 +304,13 @@ const PARSED = [
304304 pathWithoutLast: [],
305305 pathLast: "u8",
306306 generics: [],
307 typeFilter: -1,
307 typeFilter: null,
308308 },
309309 ],
310 typeFilter: 1,
310 typeFilter: "primitive",
311311 },
312312 ],
313 typeFilter: -1,
313 typeFilter: null,
314314 },
315315 ],
316316 foundElems: 1,
......@@ -339,10 +339,10 @@ const PARSED = [
339339 pathWithoutLast: [],
340340 pathLast: "u8",
341341 generics: [],
342 typeFilter: -1,
342 typeFilter: null,
343343 },
344344 ],
345 typeFilter: 1,
345 typeFilter: "primitive",
346346 },
347347 {
348348 name: "u8",
......@@ -350,10 +350,10 @@ const PARSED = [
350350 pathWithoutLast: [],
351351 pathLast: "u8",
352352 generics: [],
353 typeFilter: -1,
353 typeFilter: null,
354354 },
355355 ],
356 typeFilter: -1,
356 typeFilter: null,
357357 },
358358 ],
359359 foundElems: 1,
......@@ -382,13 +382,13 @@ const PARSED = [
382382 pathWithoutLast: [],
383383 pathLast: "u8",
384384 generics: [],
385 typeFilter: -1,
385 typeFilter: null,
386386 },
387387 ],
388 typeFilter: 1,
388 typeFilter: "primitive",
389389 },
390390 ],
391 typeFilter: -1,
391 typeFilter: null,
392392 },
393393 ],
394394 foundElems: 1,
......@@ -417,7 +417,7 @@ const PARSED = [
417417 pathWithoutLast: [],
418418 pathLast: "mut",
419419 generics: [],
420 typeFilter: 0,
420 typeFilter: "keyword",
421421 },
422422 {
423423 name: "u8",
......@@ -425,10 +425,10 @@ const PARSED = [
425425 pathWithoutLast: [],
426426 pathLast: "u8",
427427 generics: [],
428 typeFilter: -1,
428 typeFilter: null,
429429 },
430430 ],
431 typeFilter: 1,
431 typeFilter: "primitive",
432432 },
433433 {
434434 name: "u8",
......@@ -436,10 +436,10 @@ const PARSED = [
436436 pathWithoutLast: [],
437437 pathLast: "u8",
438438 generics: [],
439 typeFilter: -1,
439 typeFilter: null,
440440 },
441441 ],
442 typeFilter: -1,
442 typeFilter: null,
443443 },
444444 ],
445445 foundElems: 1,
......@@ -462,10 +462,10 @@ const PARSED = [
462462 pathWithoutLast: [],
463463 pathLast: "u8",
464464 generics: [],
465 typeFilter: -1,
465 typeFilter: null,
466466 },
467467 ],
468 typeFilter: 1,
468 typeFilter: "primitive",
469469 },
470470 ],
471471 foundElems: 1,
......@@ -496,10 +496,10 @@ const PARSED = [
496496 pathWithoutLast: [],
497497 pathLast: "u8",
498498 generics: [],
499 typeFilter: 16,
499 typeFilter: "macro",
500500 },
501501 ],
502 typeFilter: 1,
502 typeFilter: "primitive",
503503 },
504504 ],
505505 foundElems: 1,
tests/rustdoc-js-std/parser-returned.js+10-10
......@@ -18,7 +18,7 @@ const PARSED = [
1818 generics: [],
1919 },
2020 ],
21 typeFilter: -1,
21 typeFilter: null,
2222 }],
2323 error: null,
2424 },
......@@ -33,7 +33,7 @@ const PARSED = [
3333 pathWithoutLast: [],
3434 pathLast: "p",
3535 generics: [],
36 typeFilter: -1,
36 typeFilter: null,
3737 }],
3838 error: null,
3939 },
......@@ -48,7 +48,7 @@ const PARSED = [
4848 pathWithoutLast: [],
4949 pathLast: "a",
5050 generics: [],
51 typeFilter: -1,
51 typeFilter: null,
5252 }],
5353 error: null,
5454 },
......@@ -60,7 +60,7 @@ const PARSED = [
6060 pathWithoutLast: [],
6161 pathLast: "aaaaa",
6262 generics: [],
63 typeFilter: -1,
63 typeFilter: null,
6464 }],
6565 foundElems: 2,
6666 userQuery: "aaaaa->a",
......@@ -70,7 +70,7 @@ const PARSED = [
7070 pathWithoutLast: [],
7171 pathLast: "a",
7272 generics: [],
73 typeFilter: -1,
73 typeFilter: null,
7474 }],
7575 error: null,
7676 },
......@@ -85,7 +85,7 @@ const PARSED = [
8585 pathWithoutLast: [],
8686 pathLast: "never",
8787 generics: [],
88 typeFilter: 1,
88 typeFilter: "primitive",
8989 }],
9090 error: null,
9191 },
......@@ -97,7 +97,7 @@ const PARSED = [
9797 pathWithoutLast: [],
9898 pathLast: "a",
9999 generics: [],
100 typeFilter: -1,
100 typeFilter: null,
101101 }],
102102 foundElems: 1,
103103 userQuery: "a->",
......@@ -113,7 +113,7 @@ const PARSED = [
113113 pathWithoutLast: [],
114114 pathLast: "never",
115115 generics: [],
116 typeFilter: 1,
116 typeFilter: "primitive",
117117 }],
118118 foundElems: 1,
119119 userQuery: "!->",
......@@ -129,7 +129,7 @@ const PARSED = [
129129 pathWithoutLast: [],
130130 pathLast: "never",
131131 generics: [],
132 typeFilter: 1,
132 typeFilter: "primitive",
133133 }],
134134 foundElems: 1,
135135 userQuery: "! ->",
......@@ -145,7 +145,7 @@ const PARSED = [
145145 pathWithoutLast: [],
146146 pathLast: "never",
147147 generics: [],
148 typeFilter: 1,
148 typeFilter: "primitive",
149149 }],
150150 foundElems: 1,
151151 userQuery: "primitive:!->",
tests/rustdoc-js-std/parser-separators.js+10-10
......@@ -10,7 +10,7 @@ const PARSED = [
1010 pathWithoutLast: ['aaaaaa'],
1111 pathLast: 'b',
1212 generics: [],
13 typeFilter: -1,
13 typeFilter: null,
1414 },
1515 ],
1616 foundElems: 1,
......@@ -27,7 +27,7 @@ const PARSED = [
2727 pathWithoutLast: [],
2828 pathLast: 'aaaaaa',
2929 generics: [],
30 typeFilter: -1,
30 typeFilter: null,
3131 },
3232 {
3333 name: 'b',
......@@ -35,7 +35,7 @@ const PARSED = [
3535 pathWithoutLast: [],
3636 pathLast: 'b',
3737 generics: [],
38 typeFilter: -1,
38 typeFilter: null,
3939 },
4040 ],
4141 foundElems: 2,
......@@ -52,7 +52,7 @@ const PARSED = [
5252 pathWithoutLast: ['a'],
5353 pathLast: 'b',
5454 generics: [],
55 typeFilter: -1,
55 typeFilter: null,
5656 },
5757 ],
5858 foundElems: 1,
......@@ -69,7 +69,7 @@ const PARSED = [
6969 pathWithoutLast: [],
7070 pathLast: 'a',
7171 generics: [],
72 typeFilter: -1,
72 typeFilter: null,
7373 },
7474 {
7575 name: 'b',
......@@ -77,7 +77,7 @@ const PARSED = [
7777 pathWithoutLast: [],
7878 pathLast: 'b',
7979 generics: [],
80 typeFilter: -1,
80 typeFilter: null,
8181 },
8282 ],
8383 foundElems: 2,
......@@ -94,7 +94,7 @@ const PARSED = [
9494 pathWithoutLast: ['a'],
9595 pathLast: 'b',
9696 generics: [],
97 typeFilter: -1,
97 typeFilter: null,
9898 },
9999 ],
100100 foundElems: 1,
......@@ -119,7 +119,7 @@ const PARSED = [
119119 generics: [],
120120 },
121121 ],
122 typeFilter: -1,
122 typeFilter: null,
123123 },
124124 ],
125125 foundElems: 1,
......@@ -151,7 +151,7 @@ const PARSED = [
151151 generics: [],
152152 },
153153 ],
154 typeFilter: -1,
154 typeFilter: null,
155155 },
156156 ],
157157 foundElems: 1,
......@@ -176,7 +176,7 @@ const PARSED = [
176176 generics: [],
177177 },
178178 ],
179 typeFilter: -1,
179 typeFilter: null,
180180 },
181181 ],
182182 foundElems: 1,
tests/rustdoc-js-std/parser-slice-array.js+18-18
......@@ -34,7 +34,7 @@ const PARSED = [
3434 pathWithoutLast: [],
3535 pathLast: "d",
3636 generics: [],
37 typeFilter: -1,
37 typeFilter: null,
3838 },
3939 {
4040 name: "[]",
......@@ -42,16 +42,16 @@ const PARSED = [
4242 pathWithoutLast: [],
4343 pathLast: "[]",
4444 generics: [],
45 typeFilter: 1,
45 typeFilter: "primitive",
4646 },
4747 ],
48 typeFilter: 1,
48 typeFilter: "primitive",
4949 },
5050 ],
51 typeFilter: 1,
51 typeFilter: "primitive",
5252 },
5353 ],
54 typeFilter: 1,
54 typeFilter: "primitive",
5555 },
5656 ],
5757 foundElems: 1,
......@@ -68,7 +68,7 @@ const PARSED = [
6868 pathWithoutLast: [],
6969 pathLast: "[]",
7070 generics: [],
71 typeFilter: 1,
71 typeFilter: "primitive",
7272 },
7373 {
7474 name: "u8",
......@@ -76,7 +76,7 @@ const PARSED = [
7676 pathWithoutLast: [],
7777 pathLast: "u8",
7878 generics: [],
79 typeFilter: -1,
79 typeFilter: null,
8080 },
8181 ],
8282 foundElems: 2,
......@@ -99,10 +99,10 @@ const PARSED = [
9999 pathWithoutLast: [],
100100 pathLast: "u8",
101101 generics: [],
102 typeFilter: -1,
102 typeFilter: null,
103103 },
104104 ],
105 typeFilter: 1,
105 typeFilter: "primitive",
106106 },
107107 ],
108108 foundElems: 1,
......@@ -125,7 +125,7 @@ const PARSED = [
125125 pathWithoutLast: [],
126126 pathLast: "u8",
127127 generics: [],
128 typeFilter: -1,
128 typeFilter: null,
129129 },
130130 {
131131 name: "u8",
......@@ -133,10 +133,10 @@ const PARSED = [
133133 pathWithoutLast: [],
134134 pathLast: "u8",
135135 generics: [],
136 typeFilter: -1,
136 typeFilter: null,
137137 },
138138 ],
139 typeFilter: 1,
139 typeFilter: "primitive",
140140 },
141141 ],
142142 foundElems: 1,
......@@ -165,13 +165,13 @@ const PARSED = [
165165 pathWithoutLast: [],
166166 pathLast: "u8",
167167 generics: [],
168 typeFilter: -1,
168 typeFilter: null,
169169 },
170170 ],
171 typeFilter: -1,
171 typeFilter: null,
172172 },
173173 ],
174 typeFilter: 1,
174 typeFilter: "primitive",
175175 },
176176 ],
177177 foundElems: 1,
......@@ -188,7 +188,7 @@ const PARSED = [
188188 pathWithoutLast: [],
189189 pathLast: "[]",
190190 generics: [],
191 typeFilter: 1,
191 typeFilter: "primitive",
192192 },
193193 ],
194194 foundElems: 1,
......@@ -283,10 +283,10 @@ const PARSED = [
283283 pathWithoutLast: [],
284284 pathLast: "u8",
285285 generics: [],
286 typeFilter: -1,
286 typeFilter: null,
287287 },
288288 ],
289 typeFilter: 1,
289 typeFilter: "primitive",
290290 },
291291 ],
292292 foundElems: 1,
tests/rustdoc-js-std/parser-tuple.js+19-19
......@@ -22,7 +22,7 @@ const PARSED = [
2222 pathWithoutLast: [],
2323 pathLast: "d",
2424 generics: [],
25 typeFilter: -1,
25 typeFilter: null,
2626 },
2727 {
2828 name: "()",
......@@ -30,10 +30,10 @@ const PARSED = [
3030 pathWithoutLast: [],
3131 pathLast: "()",
3232 generics: [],
33 typeFilter: 1,
33 typeFilter: "primitive",
3434 },
3535 ],
36 typeFilter: 1,
36 typeFilter: "primitive",
3737 }
3838 ],
3939 foundElems: 1,
......@@ -50,7 +50,7 @@ const PARSED = [
5050 pathWithoutLast: [],
5151 pathLast: "()",
5252 generics: [],
53 typeFilter: 1,
53 typeFilter: "primitive",
5454 },
5555 {
5656 name: "u8",
......@@ -58,7 +58,7 @@ const PARSED = [
5858 pathWithoutLast: [],
5959 pathLast: "u8",
6060 generics: [],
61 typeFilter: -1,
61 typeFilter: null,
6262 },
6363 ],
6464 foundElems: 2,
......@@ -81,7 +81,7 @@ const PARSED = [
8181 pathWithoutLast: [],
8282 pathLast: "u8",
8383 generics: [],
84 typeFilter: -1,
84 typeFilter: null,
8585 },
8686 ],
8787 foundElems: 1,
......@@ -104,10 +104,10 @@ const PARSED = [
104104 pathWithoutLast: [],
105105 pathLast: "u8",
106106 generics: [],
107 typeFilter: -1,
107 typeFilter: null,
108108 },
109109 ],
110 typeFilter: 1,
110 typeFilter: "primitive",
111111 },
112112 ],
113113 foundElems: 1,
......@@ -130,10 +130,10 @@ const PARSED = [
130130 pathWithoutLast: [],
131131 pathLast: "u8",
132132 generics: [],
133 typeFilter: -1,
133 typeFilter: null,
134134 },
135135 ],
136 typeFilter: 1,
136 typeFilter: "primitive",
137137 },
138138 ],
139139 foundElems: 1,
......@@ -156,10 +156,10 @@ const PARSED = [
156156 pathWithoutLast: [],
157157 pathLast: "u8",
158158 generics: [],
159 typeFilter: -1,
159 typeFilter: null,
160160 },
161161 ],
162 typeFilter: 1,
162 typeFilter: "primitive",
163163 },
164164 ],
165165 foundElems: 1,
......@@ -176,7 +176,7 @@ const PARSED = [
176176 pathWithoutLast: [],
177177 pathLast: "u8",
178178 generics: [],
179 typeFilter: 1,
179 typeFilter: "primitive",
180180 },
181181 ],
182182 foundElems: 1,
......@@ -199,7 +199,7 @@ const PARSED = [
199199 pathWithoutLast: [],
200200 pathLast: "u8",
201201 generics: [],
202 typeFilter: -1,
202 typeFilter: null,
203203 },
204204 {
205205 name: "u8",
......@@ -207,10 +207,10 @@ const PARSED = [
207207 pathWithoutLast: [],
208208 pathLast: "u8",
209209 generics: [],
210 typeFilter: -1,
210 typeFilter: null,
211211 },
212212 ],
213 typeFilter: 1,
213 typeFilter: "primitive",
214214 },
215215 ],
216216 foundElems: 1,
......@@ -233,10 +233,10 @@ const PARSED = [
233233 pathWithoutLast: [],
234234 pathLast: "u8",
235235 generics: [],
236 typeFilter: -1,
236 typeFilter: null,
237237 },
238238 ],
239 typeFilter: -1,
239 typeFilter: null,
240240 },
241241 ],
242242 foundElems: 1,
......@@ -253,7 +253,7 @@ const PARSED = [
253253 pathWithoutLast: [],
254254 pathLast: "()",
255255 generics: [],
256 typeFilter: 1,
256 typeFilter: "primitive",
257257 },
258258 ],
259259 foundElems: 1,
tests/rustdoc-js-std/path-end-empty.js+2-1
......@@ -1,6 +1,7 @@
1const FILTER_CRATE = "std";
12const EXPECTED = {
23 'query': 'Option::',
34 'others': [
4 { 'path': 'std::option::Option', 'name': 'get_or_insert_default' },
5 { 'path': 'std::option::Option', 'name': 'eq' },
56 ],
67}
tests/rustdoc-js-std/path-maxeditdistance.js+3-3
......@@ -10,15 +10,15 @@ const EXPECTED = [
1010 query: 'vec::iter',
1111 others: [
1212 // std::net::ToSocketAttrs::iter should not show up here
13 { 'path': 'std::vec', 'name': 'IntoIter' },
13 { 'path': 'std::collections::VecDeque', 'name': 'iter' },
14 { 'path': 'std::collections::VecDeque', 'name': 'iter_mut' },
1415 { 'path': 'std::vec::Vec', 'name': 'from_iter' },
16 { 'path': 'std::vec', 'name': 'IntoIter' },
1517 { 'path': 'std::vec::Vec', 'name': 'into_iter' },
1618 { 'path': 'std::vec::ExtractIf', 'name': 'into_iter' },
1719 { 'path': 'std::vec::Drain', 'name': 'into_iter' },
1820 { 'path': 'std::vec::IntoIter', 'name': 'into_iter' },
1921 { 'path': 'std::vec::Splice', 'name': 'into_iter' },
20 { 'path': 'std::collections::VecDeque', 'name': 'iter' },
21 { 'path': 'std::collections::VecDeque', 'name': 'iter_mut' },
2222 { 'path': 'std::collections::VecDeque', 'name': 'from_iter' },
2323 { 'path': 'std::collections::VecDeque', 'name': 'into_iter' },
2424 ],
tests/rustdoc-js-std/return-specific-literal.js+1-1
......@@ -4,6 +4,6 @@ const EXPECTED = {
44 { 'path': 'std::string::String', 'name': 'ne' },
55 ],
66 'returned': [
7 { 'path': 'std::string::String', 'name': 'add' },
7 { 'path': 'std::string::String', 'name': 'new' },
88 ],
99};
tests/rustdoc-js-std/return-specific.js+1-1
......@@ -4,6 +4,6 @@ const EXPECTED = {
44 { 'path': 'std::string::String', 'name': 'ne' },
55 ],
66 'returned': [
7 { 'path': 'std::string::String', 'name': 'add' },
7 { 'path': 'std::string::String', 'name': 'new' },
88 ],
99};
tests/rustdoc-js/doc-alias.js+6-7
......@@ -231,6 +231,12 @@ const EXPECTED = [
231231 {
232232 'query': 'UnionItem',
233233 'others': [
234 {
235 'path': 'doc_alias::Union',
236 'name': 'union_item',
237 'desc': 'Doc for <code>Union::union_item</code>',
238 'href': '../doc_alias/union.Union.html#structfield.union_item'
239 },
234240 {
235241 'path': 'doc_alias',
236242 'name': 'Union',
......@@ -239,13 +245,6 @@ const EXPECTED = [
239245 'href': '../doc_alias/union.Union.html',
240246 'is_alias': true
241247 },
242 // Not an alias!
243 {
244 'path': 'doc_alias::Union',
245 'name': 'union_item',
246 'desc': 'Doc for <code>Union::union_item</code>',
247 'href': '../doc_alias/union.Union.html#structfield.union_item'
248 },
249248 ],
250249 },
251250 {
tests/rustdoc-js/generics-trait.js+19-2
......@@ -25,8 +25,25 @@ const EXPECTED = [
2525 },
2626 {
2727 'query': 'Resulx<SomeTrait>',
28 'in_args': [],
29 'returned': [],
28 'correction': 'Result',
29 'in_args': [
30 {
31 'path': 'generics_trait',
32 'name': 'beta',
33 'displayType': '`Result`<`T`, ()> -> ()',
34 'displayMappedNames': '',
35 'displayWhereClause': 'T: `SomeTrait`',
36 },
37 ],
38 'returned': [
39 {
40 'path': 'generics_trait',
41 'name': 'bet',
42 'displayType': ' -> `Result`<`T`, ()>',
43 'displayMappedNames': '',
44 'displayWhereClause': 'T: `SomeTrait`',
45 },
46 ],
3047 },
3148 {
3249 'query': 'Result<SomeTraiz>',
tests/rustdoc-js/non-english-identifier.js+4-4
......@@ -7,7 +7,7 @@ const PARSED = [
77 pathWithoutLast: [],
88 pathLast: "中文",
99 generics: [],
10 typeFilter: -1,
10 typeFilter: null,
1111 }],
1212 returned: [],
1313 foundElems: 1,
......@@ -23,7 +23,7 @@ const PARSED = [
2323 pathLast: "_0mixed中英文",
2424 normalizedPathLast: "0mixed中英文",
2525 generics: [],
26 typeFilter: -1,
26 typeFilter: null,
2727 }],
2828 foundElems: 1,
2929 userQuery: "_0Mixed中英文",
......@@ -38,7 +38,7 @@ const PARSED = [
3838 pathWithoutLast: ["my_crate"],
3939 pathLast: "中文api",
4040 generics: [],
41 typeFilter: -1,
41 typeFilter: null,
4242 }],
4343 foundElems: 1,
4444 userQuery: "my_crate::中文API",
......@@ -94,7 +94,7 @@ const PARSED = [
9494 pathWithoutLast: ["my_crate"],
9595 pathLast: "中文宏",
9696 generics: [],
97 typeFilter: 16,
97 typeFilter: "macro",
9898 }],
9999 foundElems: 1,
100100 userQuery: "my_crate 中文宏!",
tests/rustdoc-js/ordering.js created+9
......@@ -0,0 +1,9 @@
1const EXPECTED = [
2 {
3 'query': 'Entry',
4 'others': [
5 { 'path': 'ordering', 'name': 'Entry1a' },
6 { 'path': 'ordering', 'name': 'Entry2ab' },
7 ],
8 },
9];
tests/rustdoc-js/ordering.rs created+3
......@@ -0,0 +1,3 @@
1pub struct Entry1a;
2pub struct Entry1b;
3pub struct Entry2ab;
tests/rustdoc-js/type-parameters.js+2-4
......@@ -4,8 +4,8 @@ const EXPECTED = [
44 {
55 query: '-> trait:Some',
66 others: [
7 { path: 'foo', name: 'alpha' },
87 { path: 'foo', name: 'alef' },
8 { path: 'foo', name: 'alpha' },
99 ],
1010 },
1111 {
......@@ -75,10 +75,8 @@ const EXPECTED = [
7575 {
7676 query: 'Other',
7777 in_args: [
78 // because function is called "other", it's sorted first
79 // even though it has higher type distance
80 { path: 'foo', name: 'other' },
8178 { path: 'foo', name: 'alternate' },
79 { path: 'foo', name: 'other' },
8280 ],
8381 },
8482 {
tests/rustdoc/cross-crate-info/cargo-transitive-no-index/s.rs+3-3
......@@ -5,9 +5,9 @@
55//@ has t/trait.Tango.html
66//@ hasraw s/struct.Sierra.html 'Tango'
77//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
8//@ hasraw search-index.js 'Tango'
9//@ hasraw search-index.js 'Sierra'
10//@ hasraw search-index.js 'Quebec'
8//@ hasraw search.index/name/*.js 'Tango'
9//@ hasraw search.index/name/*.js 'Sierra'
10//@ hasraw search.index/name/*.js 'Quebec'
1111
1212// We document multiple crates into the same output directory, which
1313// merges the cross-crate information. Everything is available.
tests/rustdoc/cross-crate-info/cargo-transitive/s.rs+3-3
......@@ -13,9 +13,9 @@
1313//@ has t/trait.Tango.html
1414//@ hasraw s/struct.Sierra.html 'Tango'
1515//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
16//@ hasraw search-index.js 'Tango'
17//@ hasraw search-index.js 'Sierra'
18//@ hasraw search-index.js 'Quebec'
16//@ hasraw search.index/name/*.js 'Tango'
17//@ hasraw search.index/name/*.js 'Sierra'
18//@ hasraw search.index/name/*.js 'Quebec'
1919
2020// We document multiple crates into the same output directory, which
2121// merges the cross-crate information. Everything is available.
tests/rustdoc/cross-crate-info/cargo-two-no-index/e.rs+2-2
......@@ -4,8 +4,8 @@
44//@ has f/trait.Foxtrot.html
55//@ hasraw e/enum.Echo.html 'Foxtrot'
66//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
7//@ hasraw search-index.js 'Foxtrot'
8//@ hasraw search-index.js 'Echo'
7//@ hasraw search.index/name/*.js 'Foxtrot'
8//@ hasraw search.index/name/*.js 'Echo'
99
1010// document two crates in the same way that cargo does. do not provide
1111// --enable-index-page
tests/rustdoc/cross-crate-info/cargo-two/e.rs+2-2
......@@ -11,8 +11,8 @@
1111//@ has f/trait.Foxtrot.html
1212//@ hasraw e/enum.Echo.html 'Foxtrot'
1313//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
14//@ hasraw search-index.js 'Foxtrot'
15//@ hasraw search-index.js 'Echo'
14//@ hasraw search.index/name/*.js 'Foxtrot'
15//@ hasraw search.index/name/*.js 'Echo'
1616
1717// document two crates in the same way that cargo does, writing them both
1818// into the same output directory
tests/rustdoc/cross-crate-info/index-on-last/e.rs+2-2
......@@ -11,8 +11,8 @@
1111//@ has f/trait.Foxtrot.html
1212//@ hasraw e/enum.Echo.html 'Foxtrot'
1313//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
14//@ hasraw search-index.js 'Foxtrot'
15//@ hasraw search-index.js 'Echo'
14//@ hasraw search.index/name/*.js 'Foxtrot'
15//@ hasraw search.index/name/*.js 'Echo'
1616
1717// only declare --enable-index-page to the last rustdoc invocation
1818extern crate f;
tests/rustdoc/cross-crate-info/kitchen-sink/i.rs+4-4
......@@ -19,10 +19,10 @@
1919//@ has t/trait.Tango.html
2020//@ hasraw s/struct.Sierra.html 'Tango'
2121//@ hasraw trait.impl/t/trait.Tango.js 'struct.Sierra.html'
22//@ hasraw search-index.js 'Quebec'
23//@ hasraw search-index.js 'Romeo'
24//@ hasraw search-index.js 'Sierra'
25//@ hasraw search-index.js 'Tango'
22//@ hasraw search.index/name/*.js 'Quebec'
23//@ hasraw search.index/name/*.js 'Romeo'
24//@ hasraw search.index/name/*.js 'Sierra'
25//@ hasraw search.index/name/*.js 'Tango'
2626//@ has type.impl/s/struct.Sierra.js
2727//@ hasraw type.impl/s/struct.Sierra.js 'Tango'
2828//@ hasraw type.impl/s/struct.Sierra.js 'Romeo'
tests/rustdoc/cross-crate-info/single-crate-baseline/q.rs+1-1
......@@ -6,7 +6,7 @@
66//@ has index.html '//h1' 'List of all crates'
77//@ has index.html '//ul[@class="all-items"]//a[@href="q/index.html"]' 'q'
88//@ has q/struct.Quebec.html
9//@ hasraw search-index.js 'Quebec'
9//@ hasraw search.index/name/*.js 'Quebec'
1010
1111// there's nothing cross-crate going on here
1212pub struct Quebec;
tests/rustdoc/cross-crate-info/single-crate-no-index/q.rs+1-1
......@@ -1,6 +1,6 @@
11//@ build-aux-docs
22//@ has q/struct.Quebec.html
3//@ hasraw search-index.js 'Quebec'
3//@ hasraw search.index/name/*.js 'Quebec'
44
55// there's nothing cross-crate going on here
66pub struct Quebec;
tests/rustdoc/cross-crate-info/write-docs-somewhere-else/e.rs+2-2
......@@ -4,8 +4,8 @@
44//@ !has f/trait.Foxtrot.html
55//@ hasraw e/enum.Echo.html 'Foxtrot'
66//@ hasraw trait.impl/f/trait.Foxtrot.js 'enum.Echo.html'
7//@ !hasraw search-index.js 'Foxtrot'
8//@ hasraw search-index.js 'Echo'
7//@ !hasraw search.index/name/*.js 'Foxtrot'
8//@ hasraw search.index/name/*.js 'Echo'
99
1010// test the fact that our test runner will document this crate somewhere
1111// else
tests/rustdoc/masked.rs+1-1
......@@ -7,7 +7,7 @@
77#[doc(masked)]
88extern crate masked;
99
10//@ !hasraw 'search-index.js' 'masked_method'
10//@ !hasraw 'search.index/name/*.js' 'masked_method'
1111
1212//@ !hasraw 'foo/struct.String.html' 'MaskedTrait'
1313//@ !hasraw 'foo/struct.String.html' 'MaskedBlanketTrait'
tests/rustdoc/merge-cross-crate-info/cargo-transitive-read-write/sierra.rs+3-3
......@@ -14,9 +14,9 @@
1414//@ has tango/trait.Tango.html
1515//@ hasraw sierra/struct.Sierra.html 'Tango'
1616//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
17//@ hasraw search-index.js 'Tango'
18//@ hasraw search-index.js 'Sierra'
19//@ hasraw search-index.js 'Quebec'
17//@ hasraw search.index/name/*.js 'Tango'
18//@ hasraw search.index/name/*.js 'Sierra'
19//@ hasraw search.index/name/*.js 'Quebec'
2020
2121// similar to cargo-workflow-transitive, but we use --merge=read-write,
2222// which is the default.
tests/rustdoc/merge-cross-crate-info/kitchen-sink-separate-dirs/indigo.rs+4-4
......@@ -23,10 +23,10 @@
2323//@ !has sierra/struct.Sierra.html
2424//@ !has tango/trait.Tango.html
2525//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
26//@ hasraw search-index.js 'Quebec'
27//@ hasraw search-index.js 'Romeo'
28//@ hasraw search-index.js 'Sierra'
29//@ hasraw search-index.js 'Tango'
26//@ hasraw search.index/name/*.js 'Quebec'
27//@ hasraw search.index/name/*.js 'Romeo'
28//@ hasraw search.index/name/*.js 'Sierra'
29//@ hasraw search.index/name/*.js 'Tango'
3030//@ has type.impl/sierra/struct.Sierra.js
3131//@ hasraw type.impl/sierra/struct.Sierra.js 'Tango'
3232//@ hasraw type.impl/sierra/struct.Sierra.js 'Romeo'
tests/rustdoc/merge-cross-crate-info/no-merge-separate/sierra.rs+1-1
......@@ -8,7 +8,7 @@
88//@ has sierra/struct.Sierra.html
99//@ hasraw sierra/struct.Sierra.html 'Tango'
1010//@ !has trait.impl/tango/trait.Tango.js
11//@ !has search-index.js
11//@ !has search.index/name/*.js
1212
1313// we don't generate any cross-crate info if --merge=none, even if we
1414// document crates separately
tests/rustdoc/merge-cross-crate-info/no-merge-write-anyway/sierra.rs+1-1
......@@ -10,7 +10,7 @@
1010//@ has tango/trait.Tango.html
1111//@ hasraw sierra/struct.Sierra.html 'Tango'
1212//@ !has trait.impl/tango/trait.Tango.js
13//@ !has search-index.js
13//@ !has search.index/name/*.js
1414
1515// we --merge=none, so --parts-out-dir doesn't do anything
1616extern crate tango;
tests/rustdoc/merge-cross-crate-info/overwrite-but-include/sierra.rs+3-3
......@@ -10,9 +10,9 @@
1010//@ has tango/trait.Tango.html
1111//@ hasraw sierra/struct.Sierra.html 'Tango'
1212//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
13//@ hasraw search-index.js 'Tango'
14//@ hasraw search-index.js 'Sierra'
15//@ !hasraw search-index.js 'Quebec'
13//@ hasraw search.index/name/*.js 'Tango'
14//@ hasraw search.index/name/*.js 'Sierra'
15//@ !hasraw search.index/name/*.js 'Quebec'
1616
1717// we overwrite quebec and tango's cross-crate information, but we
1818// include the info from tango meaning that it should appear in the out
tests/rustdoc/merge-cross-crate-info/overwrite-but-separate/sierra.rs+3-3
......@@ -13,9 +13,9 @@
1313//@ has index.html '//ul[@class="all-items"]//a[@href="tango/index.html"]' 'tango'
1414//@ has sierra/struct.Sierra.html
1515//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
16//@ hasraw search-index.js 'Tango'
17//@ hasraw search-index.js 'Sierra'
18//@ hasraw search-index.js 'Quebec'
16//@ hasraw search.index/name/*.js 'Tango'
17//@ hasraw search.index/name/*.js 'Sierra'
18//@ hasraw search.index/name/*.js 'Quebec'
1919
2020// If these were documeted into the same directory, the info would be
2121// overwritten. However, since they are merged, we can still recover all
tests/rustdoc/merge-cross-crate-info/overwrite/sierra.rs+3-3
......@@ -9,9 +9,9 @@
99//@ has tango/trait.Tango.html
1010//@ hasraw sierra/struct.Sierra.html 'Tango'
1111//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
12//@ hasraw search-index.js 'Tango'
13//@ hasraw search-index.js 'Sierra'
14//@ !hasraw search-index.js 'Quebec'
12//@ hasraw search.index/name/*.js 'Tango'
13//@ hasraw search.index/name/*.js 'Sierra'
14//@ !hasraw search.index/name/*.js 'Quebec'
1515
1616// since tango is documented with --merge=finalize, we overwrite q's
1717// cross-crate information
tests/rustdoc/merge-cross-crate-info/single-crate-finalize/quebec.rs+1-1
......@@ -6,7 +6,7 @@
66//@ has index.html '//h1' 'List of all crates'
77//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec'
88//@ has quebec/struct.Quebec.html
9//@ hasraw search-index.js 'Quebec'
9//@ hasraw search.index/name/*.js 'Quebec'
1010
1111// there is nothing to read from the output directory if we use a single
1212// crate
tests/rustdoc/merge-cross-crate-info/single-crate-read-write/quebec.rs+1-1
......@@ -6,7 +6,7 @@
66//@ has index.html '//h1' 'List of all crates'
77//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec'
88//@ has quebec/struct.Quebec.html
9//@ hasraw search-index.js 'Quebec'
9//@ hasraw search.index/name/*.js 'Quebec'
1010
1111// read-write is the default and this does the same as `single-crate`
1212pub struct Quebec;
tests/rustdoc/merge-cross-crate-info/single-crate-write-anyway/quebec.rs+1-1
......@@ -6,7 +6,7 @@
66//@ has index.html '//h1' 'List of all crates'
77//@ has index.html '//ul[@class="all-items"]//a[@href="quebec/index.html"]' 'quebec'
88//@ has quebec/struct.Quebec.html
9//@ hasraw search-index.js 'Quebec'
9//@ hasraw search.index/name/*.js 'Quebec'
1010
1111// we can --parts-out-dir, but that doesn't do anything other than create
1212// the file
tests/rustdoc/merge-cross-crate-info/single-merge-none-useless-write/quebec.rs+1-1
......@@ -5,7 +5,7 @@
55
66//@ !has index.html
77//@ has quebec/struct.Quebec.html
8//@ !has search-index.js
8//@ !has search.index/name/*.js
99
1010// --merge=none doesn't write anything, despite --parts-out-dir
1111pub struct Quebec;
tests/rustdoc/merge-cross-crate-info/transitive-finalize/sierra.rs+1-1
......@@ -12,7 +12,7 @@
1212//@ has tango/trait.Tango.html
1313//@ hasraw sierra/struct.Sierra.html 'Tango'
1414//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
15//@ hasraw search-index.js 'Sierra'
15//@ hasraw search.index/name/*.js 'Sierra'
1616
1717// write only overwrites stuff in the output directory
1818extern crate tango;
tests/rustdoc/merge-cross-crate-info/transitive-merge-none/sierra.rs+3-3
......@@ -16,9 +16,9 @@
1616//@ has tango/trait.Tango.html
1717//@ hasraw sierra/struct.Sierra.html 'Tango'
1818//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
19//@ hasraw search-index.js 'Tango'
20//@ hasraw search-index.js 'Sierra'
21//@ hasraw search-index.js 'Quebec'
19//@ hasraw search.index/name/*.js 'Tango'
20//@ hasraw search.index/name/*.js 'Sierra'
21//@ hasraw search.index/name/*.js 'Quebec'
2222
2323// We avoid writing any cross-crate information, preferring to include it
2424// with --include-parts-dir.
tests/rustdoc/merge-cross-crate-info/transitive-merge-read-write/sierra.rs+3-3
......@@ -14,9 +14,9 @@
1414//@ has tango/trait.Tango.html
1515//@ hasraw sierra/struct.Sierra.html 'Tango'
1616//@ hasraw trait.impl/tango/trait.Tango.js 'struct.Sierra.html'
17//@ hasraw search-index.js 'Tango'
18//@ hasraw search-index.js 'Sierra'
19//@ hasraw search-index.js 'Quebec'
17//@ hasraw search.index/name/*.js 'Tango'
18//@ hasraw search.index/name/*.js 'Sierra'
19//@ hasraw search.index/name/*.js 'Quebec'
2020
2121// We can use read-write to emulate the default behavior of rustdoc, when
2222// --merge is left out.
tests/rustdoc/merge-cross-crate-info/transitive-no-info/sierra.rs+1-1
......@@ -9,7 +9,7 @@
99//@ has tango/trait.Tango.html
1010//@ hasraw sierra/struct.Sierra.html 'Tango'
1111//@ !has trait.impl/tango/trait.Tango.js
12//@ !has search-index.js
12//@ !has search.index/name/*.js
1313
1414// --merge=none on all crates does not generate any cross-crate info
1515extern crate tango;
tests/rustdoc/merge-cross-crate-info/two-separate-out-dir/echo.rs+2-2
......@@ -6,8 +6,8 @@
66//@ has echo/enum.Echo.html
77//@ hasraw echo/enum.Echo.html 'Foxtrot'
88//@ hasraw trait.impl/foxtrot/trait.Foxtrot.js 'enum.Echo.html'
9//@ hasraw search-index.js 'Foxtrot'
10//@ hasraw search-index.js 'Echo'
9//@ hasraw search.index/name/*.js 'Foxtrot'
10//@ hasraw search.index/name/*.js 'Echo'
1111
1212// document two crates in different places, and merge their docs after
1313// they are generated
tests/rustdoc/no-unit-struct-field.rs+5-4
......@@ -1,10 +1,11 @@
11// This test ensures that the tuple struct fields are not generated in the
22// search index.
33
4//@ !hasraw search-index.js '"0"'
5//@ !hasraw search-index.js '"1"'
6//@ hasraw search-index.js '"foo_a"'
7//@ hasraw search-index.js '"bar_a"'
4// vlqhex encoding ` = 0, a = 1, e = 5
5//@ !hasraw search.index/name/*.js 'a0'
6//@ !hasraw search.index/name/*.js 'a1'
7//@ hasraw search.index/name/*.js 'efoo_a'
8//@ hasraw search.index/name/*.js 'ebar_a'
89
910pub struct Bar(pub u32, pub u8);
1011pub struct Foo {
tests/rustdoc/primitive/search-index-primitive-inherent-method-23511.rs+1-1
......@@ -9,7 +9,7 @@ pub mod str {
99 #![rustc_doc_primitive = "str"]
1010
1111 impl str {
12 //@ hasraw search-index.js foo
12 //@ hasraw search.index/name/*.js foo
1313 #[rustc_allow_incoherent_impl]
1414 pub fn foo(&self) {}
1515 }
tests/rustdoc/search-index-summaries.rs+1-1
......@@ -1,6 +1,6 @@
11#![crate_name = "foo"]
22
3//@ hasraw 'search.desc/foo/foo-desc-0-.js' 'Foo short link.'
3//@ hasraw 'search.index/desc/*.js' 'Foo short link.'
44//@ !hasraw - 'www.example.com'
55//@ !hasraw - 'More Foo.'
66
tests/rustdoc/search-index.rs+2-2
......@@ -2,7 +2,7 @@
22
33use std::ops::Deref;
44
5//@ hasraw search-index.js Foo
5//@ hasraw search.index/name/*.js Foo
66pub use private::Foo;
77
88mod private {
......@@ -20,7 +20,7 @@ mod private {
2020pub struct Bar;
2121
2222impl Deref for Bar {
23 //@ !hasraw search-index.js Target
23 //@ !hasraw search.index/name/*.js Target
2424 type Target = Bar;
2525 fn deref(&self) -> &Bar { self }
2626}
tests/ui/array-slice-vec/slice-mut-2.stderr+3-3
......@@ -4,10 +4,10 @@ error[E0596]: cannot borrow `*x` as mutable, as it is behind a `&` reference
44LL | let _ = &mut x[2..4];
55 | ^ `x` is a `&` reference, so the data it refers to cannot be borrowed as mutable
66 |
7help: consider changing this to be a mutable reference
7help: consider changing this binding's type
88 |
9LL | let x: &[isize] = &mut [1, 2, 3, 4, 5];
10 | +++
9LL | let x: &mut [isize] = &[1, 2, 3, 4, 5];
10 | +++
1111
1212error: aborting due to 1 previous error
1313
tests/ui/attributes/lint_on_root.rs+1-1
......@@ -1,7 +1,7 @@
11// NOTE: this used to panic in debug builds (by a sanity assertion)
22// and not emit any lint on release builds. See https://github.com/rust-lang/rust/issues/142891.
33#![inline = ""]
4//~^ ERROR: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]` [ill_formed_attribute_input]
4//~^ ERROR: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]` [ill_formed_attribute_input]
55//~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
66//~| ERROR attribute cannot be used on
77
tests/ui/attributes/lint_on_root.stderr+2-2
......@@ -6,7 +6,7 @@ LL | #![inline = ""]
66 |
77 = help: `#[inline]` can only be applied to functions
88
9error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]`
9error: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]`
1010 --> $DIR/lint_on_root.rs:3:1
1111 |
1212LL | #![inline = ""]
......@@ -19,7 +19,7 @@ LL | #![inline = ""]
1919error: aborting due to 2 previous errors
2020
2121Future incompatibility report: Future breakage diagnostic:
22error: valid forms for the attribute are `#[inline(always)]`, `#[inline(never)]`, and `#[inline]`
22error: valid forms for the attribute are `#![inline(always)]`, `#![inline(never)]`, and `#![inline]`
2323 --> $DIR/lint_on_root.rs:3:1
2424 |
2525LL | #![inline = ""]
tests/ui/attributes/malformed-attrs.rs+2-2
......@@ -12,7 +12,7 @@
1212#![feature(min_generic_const_args)]
1313#![feature(ffi_const, ffi_pure)]
1414#![feature(coverage_attribute)]
15#![feature(no_sanitize)]
15#![feature(sanitize)]
1616#![feature(marker_trait_attr)]
1717#![feature(thread_local)]
1818#![feature(must_not_suspend)]
......@@ -89,7 +89,7 @@
8989//~^ ERROR malformed
9090#[coverage]
9191//~^ ERROR malformed `coverage` attribute input
92#[no_sanitize]
92#[sanitize]
9393//~^ ERROR malformed
9494#[ignore()]
9595//~^ ERROR valid forms for the attribute are
tests/ui/attributes/malformed-attrs.stderr+15-3
......@@ -49,11 +49,23 @@ LL | #[crate_name]
4949 |
5050 = note: for more information, visit <https://doc.rust-lang.org/reference/crates-and-source-files.html#the-crate_name-attribute>
5151
52error: malformed `no_sanitize` attribute input
52error: malformed `sanitize` attribute input
5353 --> $DIR/malformed-attrs.rs:92:1
5454 |
55LL | #[no_sanitize]
56 | ^^^^^^^^^^^^^^ help: must be of the form: `#[no_sanitize(address, kcfi, memory, thread)]`
55LL | #[sanitize]
56 | ^^^^^^^^^^^
57 |
58help: the following are the possible correct uses
59 |
60LL | #[sanitize(address = "on|off")]
61 | ++++++++++++++++++++
62LL | #[sanitize(cfi = "on|off")]
63 | ++++++++++++++++
64LL | #[sanitize(hwaddress = "on|off")]
65 | ++++++++++++++++++++++
66LL | #[sanitize(kcfi = "on|off")]
67 | +++++++++++++++++
68 = and 5 other candidates
5769
5870error: malformed `instruction_set` attribute input
5971 --> $DIR/malformed-attrs.rs:106:1
tests/ui/attributes/malformed-reprs.stderr+8-12
......@@ -7,18 +7,14 @@ LL | #![repr]
77 = note: for more information, visit <https://doc.rust-lang.org/reference/type-layout.html#representations>
88help: try changing it to one of the following valid forms of the attribute
99 |
10LL - #![repr]
11LL + #[repr(<integer type>)]
12 |
13LL - #![repr]
14LL + #[repr(C)]
15 |
16LL - #![repr]
17LL + #[repr(Rust)]
18 |
19LL - #![repr]
20LL + #[repr(align(...))]
21 |
10LL | #![repr(<integer type>)]
11 | ++++++++++++++++
12LL | #![repr(C)]
13 | +++
14LL | #![repr(Rust)]
15 | ++++++
16LL | #![repr(align(...))]
17 | ++++++++++++
2218 = and 2 other candidates
2319
2420error[E0589]: invalid `repr(align)` attribute: not a power of two
tests/ui/attributes/no-sanitize.rs deleted-45
......@@ -1,45 +0,0 @@
1#![feature(no_sanitize)]
2#![feature(stmt_expr_attributes)]
3#![deny(unused_attributes)]
4#![allow(dead_code)]
5
6fn invalid() {
7 #[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
8 {
9 1
10 };
11}
12
13#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
14type InvalidTy = ();
15
16#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
17mod invalid_module {}
18
19fn main() {
20 let _ = #[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
21 (|| 1);
22}
23
24#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
25struct F;
26
27#[no_sanitize(memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
28impl F {
29 #[no_sanitize(memory)]
30 fn valid(&self) {}
31}
32
33#[no_sanitize(address, memory)] //~ ERROR `#[no_sanitize(memory)]` should be applied to a function
34static INVALID : i32 = 0;
35
36#[no_sanitize(memory)]
37fn valid() {}
38
39#[no_sanitize(address)]
40static VALID : i32 = 0;
41
42#[no_sanitize("address")]
43//~^ ERROR `#[no_sanitize(...)]` should be applied to a function
44//~| ERROR invalid argument for `no_sanitize`
45static VALID2 : i32 = 0;
tests/ui/attributes/no-sanitize.stderr deleted-80
......@@ -1,80 +0,0 @@
1error: `#[no_sanitize(memory)]` should be applied to a function
2 --> $DIR/no-sanitize.rs:7:19
3 |
4LL | #[no_sanitize(memory)]
5 | ^^^^^^
6LL | / {
7LL | | 1
8LL | | };
9 | |_____- not a function
10
11error: `#[no_sanitize(memory)]` should be applied to a function
12 --> $DIR/no-sanitize.rs:13:15
13 |
14LL | #[no_sanitize(memory)]
15 | ^^^^^^
16LL | type InvalidTy = ();
17 | -------------------- not a function
18
19error: `#[no_sanitize(memory)]` should be applied to a function
20 --> $DIR/no-sanitize.rs:16:15
21 |
22LL | #[no_sanitize(memory)]
23 | ^^^^^^
24LL | mod invalid_module {}
25 | --------------------- not a function
26
27error: `#[no_sanitize(memory)]` should be applied to a function
28 --> $DIR/no-sanitize.rs:20:27
29 |
30LL | let _ = #[no_sanitize(memory)]
31 | ^^^^^^
32LL | (|| 1);
33 | ------ not a function
34
35error: `#[no_sanitize(memory)]` should be applied to a function
36 --> $DIR/no-sanitize.rs:24:15
37 |
38LL | #[no_sanitize(memory)]
39 | ^^^^^^
40LL | struct F;
41 | --------- not a function
42
43error: `#[no_sanitize(memory)]` should be applied to a function
44 --> $DIR/no-sanitize.rs:27:15
45 |
46LL | #[no_sanitize(memory)]
47 | ^^^^^^
48LL | / impl F {
49LL | | #[no_sanitize(memory)]
50LL | | fn valid(&self) {}
51LL | | }
52 | |_- not a function
53
54error: `#[no_sanitize(memory)]` should be applied to a function
55 --> $DIR/no-sanitize.rs:33:24
56 |
57LL | #[no_sanitize(address, memory)]
58 | ^^^^^^
59LL | static INVALID : i32 = 0;
60 | ------------------------- not a function
61
62error: `#[no_sanitize(...)]` should be applied to a function
63 --> $DIR/no-sanitize.rs:42:15
64 |
65LL | #[no_sanitize("address")]
66 | ^^^^^^^^^
67...
68LL | static VALID2 : i32 = 0;
69 | ------------------------ not a function
70
71error: invalid argument for `no_sanitize`
72 --> $DIR/no-sanitize.rs:42:15
73 |
74LL | #[no_sanitize("address")]
75 | ^^^^^^^^^
76 |
77 = note: expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
78
79error: aborting due to 9 previous errors
80
tests/ui/borrowck/borrow-raw-address-of-deref-mutability.stderr+3-3
......@@ -15,10 +15,10 @@ error[E0596]: cannot borrow `*x` as mutable, as it is behind a `*const` pointer
1515LL | let q = &raw mut *x;
1616 | ^^^^^^^^^^^ `x` is a `*const` pointer, so the data it refers to cannot be borrowed as mutable
1717 |
18help: consider changing this to be a mutable pointer
18help: consider specifying this binding's type
1919 |
20LL | let x = &mut 0 as *const i32;
21 | +++
20LL | let x: *mut i32 = &0 as *const i32;
21 | ++++++++++
2222
2323error: aborting due to 2 previous errors
2424
tests/ui/borrowck/borrowck-access-permissions.stderr+4-3
......@@ -43,10 +43,11 @@ error[E0596]: cannot borrow `*ptr_x` as mutable, as it is behind a `*const` poin
4343LL | let _y1 = &mut *ptr_x;
4444 | ^^^^^^^^^^^ `ptr_x` is a `*const` pointer, so the data it refers to cannot be borrowed as mutable
4545 |
46help: consider changing this to be a mutable pointer
46help: consider changing this binding's type
47 |
48LL - let ptr_x: *const _ = &x;
49LL + let ptr_x: *mut i32 = &x;
4750 |
48LL | let ptr_x: *const _ = &mut x;
49 | +++
5051
5152error[E0596]: cannot borrow `*foo_ref.f` as mutable, as it is behind a `&` reference
5253 --> $DIR/borrowck-access-permissions.rs:59:18
tests/ui/borrowck/implementation-not-general-enough-ice-133252.stderr-6
......@@ -22,12 +22,6 @@ LL | force_send(async_load(&not_static));
2222...
2323LL | }
2424 | - `not_static` dropped here while still borrowed
25 |
26note: due to current limitations in the borrow checker, this implies a `'static` lifetime
27 --> $DIR/implementation-not-general-enough-ice-133252.rs:16:18
28 |
29LL | fn force_send<T: Send>(_: T) {}
30 | ^^^^
3125
3226error: aborting due to 2 previous errors
3327
tests/ui/borrowck/suggestions/overloaded-index-not-mut-but-should-be-mut.fixed created+21
......@@ -0,0 +1,21 @@
1//@ run-rustfix
2fn main() {
3 let mut map = std::collections::BTreeMap::new();
4 map.insert(0, "string".to_owned());
5
6 let string = map.get_mut(&0).unwrap();
7 string.push_str("test");
8 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
9
10 let mut map = std::collections::HashMap::new();
11 map.insert(0, "string".to_owned());
12
13 let string = map.get_mut(&0).unwrap();
14 string.push_str("test");
15 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
16
17 let mut vec = vec![String::new(), String::new()];
18 let string = &mut vec[0];
19 string.push_str("test");
20 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
21}
tests/ui/borrowck/suggestions/overloaded-index-not-mut-but-should-be-mut.rs created+21
......@@ -0,0 +1,21 @@
1//@ run-rustfix
2fn main() {
3 let mut map = std::collections::BTreeMap::new();
4 map.insert(0, "string".to_owned());
5
6 let string = &map[&0];
7 string.push_str("test");
8 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
9
10 let mut map = std::collections::HashMap::new();
11 map.insert(0, "string".to_owned());
12
13 let string = &map[&0];
14 string.push_str("test");
15 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
16
17 let mut vec = vec![String::new(), String::new()];
18 let string = &vec[0];
19 string.push_str("test");
20 //~^ ERROR cannot borrow `*string` as mutable, as it is behind a `&` reference
21}
tests/ui/borrowck/suggestions/overloaded-index-not-mut-but-should-be-mut.stderr created+38
......@@ -0,0 +1,38 @@
1error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
2 --> $DIR/overloaded-index-not-mut-but-should-be-mut.rs:7:5
3 |
4LL | string.push_str("test");
5 | ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
6 |
7help: consider using `get_mut`
8 |
9LL - let string = &map[&0];
10LL + let string = map.get_mut(&0).unwrap();
11 |
12
13error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
14 --> $DIR/overloaded-index-not-mut-but-should-be-mut.rs:14:5
15 |
16LL | string.push_str("test");
17 | ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
18 |
19help: consider using `get_mut`
20 |
21LL - let string = &map[&0];
22LL + let string = map.get_mut(&0).unwrap();
23 |
24
25error[E0596]: cannot borrow `*string` as mutable, as it is behind a `&` reference
26 --> $DIR/overloaded-index-not-mut-but-should-be-mut.rs:19:5
27 |
28LL | string.push_str("test");
29 | ^^^^^^ `string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
30 |
31help: consider changing this to be a mutable reference
32 |
33LL | let string = &mut vec[0];
34 | +++
35
36error: aborting due to 3 previous errors
37
38For more information about this error, try `rustc --explain E0596`.
tests/ui/borrowck/suggestions/overloaded-index-without-indexmut.rs created+16
......@@ -0,0 +1,16 @@
1use std::ops::Index;
2
3struct MyType;
4impl Index<usize> for MyType {
5 type Output = String;
6 fn index(&self, _idx: usize) -> &String {
7 const { &String::new() }
8 }
9}
10
11fn main() {
12 let x = MyType;
13 let y = &x[0];
14 y.push_str("");
15 //~^ ERROR cannot borrow `*y` as mutable, as it is behind a `&` reference
16}
tests/ui/borrowck/suggestions/overloaded-index-without-indexmut.stderr created+9
......@@ -0,0 +1,9 @@
1error[E0596]: cannot borrow `*y` as mutable, as it is behind a `&` reference
2 --> $DIR/overloaded-index-without-indexmut.rs:14:5
3 |
4LL | y.push_str("");
5 | ^ `y` is a `&` reference, so the data it refers to cannot be borrowed as mutable
6
7error: aborting due to 1 previous error
8
9For more information about this error, try `rustc --explain E0596`.
tests/ui/coverage-attr/name-value.stderr+2-2
......@@ -22,10 +22,10 @@ LL | #![coverage = "off"]
2222help: try changing it to one of the following valid forms of the attribute
2323 |
2424LL - #![coverage = "off"]
25LL + #[coverage(off)]
25LL + #![coverage(off)]
2626 |
2727LL - #![coverage = "off"]
28LL + #[coverage(on)]
28LL + #![coverage(on)]
2929 |
3030
3131error[E0539]: malformed `coverage` attribute input
tests/ui/coverage-attr/word-only.stderr+4-6
......@@ -19,12 +19,10 @@ LL | #![coverage]
1919 |
2020help: try changing it to one of the following valid forms of the attribute
2121 |
22LL - #![coverage]
23LL + #[coverage(off)]
24 |
25LL - #![coverage]
26LL + #[coverage(on)]
27 |
22LL | #![coverage(off)]
23 | +++++
24LL | #![coverage(on)]
25 | ++++
2826
2927error[E0539]: malformed `coverage` attribute input
3028 --> $DIR/word-only.rs:21:1
tests/ui/deriving/deriving-all-codegen.rs+15-3
......@@ -18,6 +18,8 @@
1818#![allow(deprecated)]
1919#![feature(derive_from)]
2020
21use std::from::From;
22
2123// Empty struct.
2224#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
2325struct Empty;
......@@ -51,7 +53,14 @@ struct SingleField {
5153// `clone` implemention that just does `*self`.
5254#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
5355struct Big {
54 b1: u32, b2: u32, b3: u32, b4: u32, b5: u32, b6: u32, b7: u32, b8: u32,
56 b1: u32,
57 b2: u32,
58 b3: u32,
59 b4: u32,
60 b5: u32,
61 b6: u32,
62 b7: u32,
63 b8: u32,
5564}
5665
5766// It is more efficient to compare scalar types before non-scalar types.
......@@ -126,7 +135,7 @@ enum Enum0 {}
126135// A single-variant enum.
127136#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
128137enum Enum1 {
129 Single { x: u32 }
138 Single { x: u32 },
130139}
131140
132141// A C-like, fieldless enum with a single variant.
......@@ -152,7 +161,10 @@ enum Mixed {
152161 P,
153162 Q,
154163 R(u32),
155 S { d1: Option<u32>, d2: Option<i32> },
164 S {
165 d1: Option<u32>,
166 d2: Option<i32>,
167 },
156168}
157169
158170// When comparing enum variant it is more efficient to compare scalar types before non-scalar types.
tests/ui/deriving/deriving-all-codegen.stdout+2
......@@ -23,6 +23,8 @@ extern crate std;
2323#[prelude_import]
2424use std::prelude::rust_2021::*;
2525
26use std::from::From;
27
2628// Empty struct.
2729struct Empty;
2830#[automatically_derived]
tests/ui/deriving/deriving-from-wrong-target.rs+2-1
......@@ -1,9 +1,10 @@
1//@ edition: 2021
21//@ check-fail
32
43#![feature(derive_from)]
54#![allow(dead_code)]
65
6use std::from::From;
7
78#[derive(From)]
89//~^ ERROR `#[derive(From)]` used on a struct with no fields
910struct S1;
tests/ui/deriving/deriving-from-wrong-target.stderr+9-9
......@@ -1,5 +1,5 @@
11error: `#[derive(From)]` used on a struct with no fields
2 --> $DIR/deriving-from-wrong-target.rs:7:10
2 --> $DIR/deriving-from-wrong-target.rs:8:10
33 |
44LL | #[derive(From)]
55 | ^^^^
......@@ -10,7 +10,7 @@ LL | struct S1;
1010 = note: `#[derive(From)]` can only be used on structs with exactly one field
1111
1212error: `#[derive(From)]` used on a struct with no fields
13 --> $DIR/deriving-from-wrong-target.rs:11:10
13 --> $DIR/deriving-from-wrong-target.rs:12:10
1414 |
1515LL | #[derive(From)]
1616 | ^^^^
......@@ -21,7 +21,7 @@ LL | struct S2 {}
2121 = note: `#[derive(From)]` can only be used on structs with exactly one field
2222
2323error: `#[derive(From)]` used on a struct with multiple fields
24 --> $DIR/deriving-from-wrong-target.rs:15:10
24 --> $DIR/deriving-from-wrong-target.rs:16:10
2525 |
2626LL | #[derive(From)]
2727 | ^^^^
......@@ -32,7 +32,7 @@ LL | struct S3(u32, bool);
3232 = note: `#[derive(From)]` can only be used on structs with exactly one field
3333
3434error: `#[derive(From)]` used on a struct with multiple fields
35 --> $DIR/deriving-from-wrong-target.rs:19:10
35 --> $DIR/deriving-from-wrong-target.rs:20:10
3636 |
3737LL | #[derive(From)]
3838 | ^^^^
......@@ -43,7 +43,7 @@ LL | struct S4 {
4343 = note: `#[derive(From)]` can only be used on structs with exactly one field
4444
4545error: `#[derive(From)]` used on an enum
46 --> $DIR/deriving-from-wrong-target.rs:26:10
46 --> $DIR/deriving-from-wrong-target.rs:27:10
4747 |
4848LL | #[derive(From)]
4949 | ^^^^
......@@ -54,7 +54,7 @@ LL | enum E1 {}
5454 = note: `#[derive(From)]` can only be used on structs with exactly one field
5555
5656error[E0277]: the size for values of type `T` cannot be known at compilation time
57 --> $DIR/deriving-from-wrong-target.rs:30:10
57 --> $DIR/deriving-from-wrong-target.rs:31:10
5858 |
5959LL | #[derive(From)]
6060 | ^^^^ doesn't have a size known at compile-time
......@@ -71,7 +71,7 @@ LL + struct SUnsizedField<T> {
7171 |
7272
7373error[E0277]: the size for values of type `T` cannot be known at compilation time
74 --> $DIR/deriving-from-wrong-target.rs:30:10
74 --> $DIR/deriving-from-wrong-target.rs:31:10
7575 |
7676LL | #[derive(From)]
7777 | ^^^^ doesn't have a size known at compile-time
......@@ -80,7 +80,7 @@ LL | struct SUnsizedField<T: ?Sized> {
8080 | - this type parameter needs to be `Sized`
8181 |
8282note: required because it appears within the type `SUnsizedField<T>`
83 --> $DIR/deriving-from-wrong-target.rs:33:8
83 --> $DIR/deriving-from-wrong-target.rs:34:8
8484 |
8585LL | struct SUnsizedField<T: ?Sized> {
8686 | ^^^^^^^^^^^^^
......@@ -92,7 +92,7 @@ LL + struct SUnsizedField<T> {
9292 |
9393
9494error[E0277]: the size for values of type `T` cannot be known at compilation time
95 --> $DIR/deriving-from-wrong-target.rs:34:11
95 --> $DIR/deriving-from-wrong-target.rs:35:11
9696 |
9797LL | struct SUnsizedField<T: ?Sized> {
9898 | - this type parameter needs to be `Sized`
tests/ui/deriving/deriving-from.rs+2
......@@ -3,6 +3,8 @@
33
44#![feature(derive_from)]
55
6use core::from::From;
7
68#[derive(From)]
79struct TupleSimple(u32);
810
tests/ui/feature-gates/feature-gate-derive-from.rs+1-1
......@@ -1,4 +1,4 @@
1//@ edition: 2021
1use std::from::From; //~ ERROR use of unstable library feature `derive_from
22
33#[derive(From)] //~ ERROR use of unstable library feature `derive_from`
44struct Foo(u32);
tests/ui/feature-gates/feature-gate-derive-from.stderr+11-1
......@@ -8,6 +8,16 @@ LL | #[derive(From)]
88 = help: add `#![feature(derive_from)]` to the crate attributes to enable
99 = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
1010
11error: aborting due to 1 previous error
11error[E0658]: use of unstable library feature `derive_from`
12 --> $DIR/feature-gate-derive-from.rs:1:5
13 |
14LL | use std::from::From;
15 | ^^^^^^^^^^^^^^^
16 |
17 = note: see issue #144889 <https://github.com/rust-lang/rust/issues/144889> for more information
18 = help: add `#![feature(derive_from)]` to the crate attributes to enable
19 = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
20
21error: aborting due to 2 previous errors
1222
1323For more information about this error, try `rustc --explain E0658`.
tests/ui/feature-gates/feature-gate-no_sanitize.rs deleted-4
......@@ -1,4 +0,0 @@
1#[no_sanitize(address)]
2//~^ ERROR the `#[no_sanitize]` attribute is an experimental feature
3fn main() {
4}
tests/ui/feature-gates/feature-gate-no_sanitize.stderr deleted-13
......@@ -1,13 +0,0 @@
1error[E0658]: the `#[no_sanitize]` attribute is an experimental feature
2 --> $DIR/feature-gate-no_sanitize.rs:1:1
3 |
4LL | #[no_sanitize(address)]
5 | ^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 = note: see issue #39699 <https://github.com/rust-lang/rust/issues/39699> for more information
8 = help: add `#![feature(no_sanitize)]` to the crate attributes to enable
9 = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
10
11error: aborting due to 1 previous error
12
13For more information about this error, try `rustc --explain E0658`.
tests/ui/feature-gates/feature-gate-sanitize.rs created+7
......@@ -0,0 +1,7 @@
1//@ normalize-stderr: "you are using [0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?( \([^)]*\))?" -> "you are using $$RUSTC_VERSION"
2#![feature(no_sanitize)] //~ ERROR feature has been removed
3
4#[sanitize(address = "on")]
5//~^ ERROR the `#[sanitize]` attribute is an experimental feature
6fn main() {
7}
tests/ui/feature-gates/feature-gate-sanitize.stderr created+23
......@@ -0,0 +1,23 @@
1error[E0557]: feature has been removed
2 --> $DIR/feature-gate-sanitize.rs:2:12
3 |
4LL | #![feature(no_sanitize)]
5 | ^^^^^^^^^^^ feature has been removed
6 |
7 = note: removed in CURRENT_RUSTC_VERSION; see <https://github.com/rust-lang/rust/pull/142681> for more information
8 = note: renamed to sanitize(xyz = "on|off")
9
10error[E0658]: the `#[sanitize]` attribute is an experimental feature
11 --> $DIR/feature-gate-sanitize.rs:4:1
12 |
13LL | #[sanitize(address = "on")]
14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 |
16 = note: see issue #39699 <https://github.com/rust-lang/rust/issues/39699> for more information
17 = help: add `#![feature(sanitize)]` to the crate attributes to enable
18 = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date
19
20error: aborting due to 2 previous errors
21
22Some errors have detailed explanations: E0557, E0658.
23For more information about an error, try `rustc --explain E0557`.
tests/ui/generic-associated-types/bugs/hrtb-implied-1.stderr+3-3
......@@ -9,11 +9,11 @@ LL | print_items::<WindowsMut<'_>>(windows);
99LL | }
1010 | - temporary value is freed at the end of this statement
1111 |
12note: due to current limitations in the borrow checker, this implies a `'static` lifetime
13 --> $DIR/hrtb-implied-1.rs:26:26
12note: due to a current limitation of the type system, this implies a `'static` lifetime
13 --> $DIR/hrtb-implied-1.rs:26:5
1414 |
1515LL | for<'a> I::Item<'a>: Debug,
16 | ^^^^^
16 | ^^^^^^^^^^^^^^^^^^^^^^^^^^
1717
1818error: aborting due to 1 previous error
1919
tests/ui/generic-associated-types/bugs/hrtb-implied-2.stderr+5-1
......@@ -15,7 +15,11 @@ LL | let _next = iter2.next();
1515 = note: requirement occurs because of a mutable reference to `Eat<&mut I, F>`
1616 = note: mutable references are invariant over their type parameter
1717 = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
18 = note: due to current limitations in the borrow checker, this implies a `'static` lifetime
18note: due to a current limitation of the type system, this implies a `'static` lifetime
19 --> $DIR/hrtb-implied-2.rs:31:8
20 |
21LL | F: FnMut(I::Item<'_>),
22 | ^^^^^^^^^^^^^^^^^^
1923
2024error: aborting due to 1 previous error
2125
tests/ui/generic-associated-types/bugs/hrtb-implied-3.stderr+3-3
......@@ -11,11 +11,11 @@ LL | trivial_bound(iter);
1111 | `iter` escapes the function body here
1212 | argument requires that `'1` must outlive `'static`
1313 |
14note: due to current limitations in the borrow checker, this implies a `'static` lifetime
15 --> $DIR/hrtb-implied-3.rs:14:26
14note: due to a current limitation of the type system, this implies a `'static` lifetime
15 --> $DIR/hrtb-implied-3.rs:14:5
1616 |
1717LL | for<'a> I::Item<'a>: Sized,
18 | ^^^^^
18 | ^^^^^^^^^^^^^^^^^^^^^^^^^^
1919
2020error: aborting due to 1 previous error
2121
tests/ui/generic-associated-types/collectivity-regression.stderr+1-1
......@@ -7,7 +7,7 @@ LL | | let _x = x;
77LL | | };
88 | |_____^
99 |
10note: due to current limitations in the borrow checker, this implies a `'static` lifetime
10note: due to a current limitation of the type system, this implies a `'static` lifetime
1111 --> $DIR/collectivity-regression.rs:11:16
1212 |
1313LL | for<'a> T: Get<Value<'a> = ()>,
tests/ui/generic-associated-types/extended/lending_iterator.stderr+6
......@@ -12,6 +12,12 @@ error: `Self` does not live long enough
1212 |
1313LL | <B as FromLendingIterator<A>>::from_iter(self)
1414 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15 |
16note: due to a current limitation of the type system, this implies a `'static` lifetime
17 --> $DIR/lending_iterator.rs:4:21
18 |
19LL | fn from_iter<T: for<'x> LendingIterator<Item<'x> = A>>(iter: T) -> Self;
20 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
1521
1622error: aborting due to 2 previous errors
1723
tests/ui/higher-ranked/trait-bounds/hrtb-just-for-static.stderr+1-1
......@@ -15,7 +15,7 @@ LL | fn give_some<'a>() {
1515LL | want_hrtb::<&'a u32>()
1616 | ^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
1717 |
18note: due to current limitations in the borrow checker, this implies a `'static` lifetime
18note: due to a current limitation of the type system, this implies a `'static` lifetime
1919 --> $DIR/hrtb-just-for-static.rs:9:15
2020 |
2121LL | where T : for<'a> Foo<&'a isize>
tests/ui/higher-ranked/trait-bounds/hrtb-perfect-forwarding.stderr+1-1
......@@ -47,7 +47,7 @@ LL | fn foo_hrtb_bar_not<'b, T>(mut t: T)
4747LL | foo_hrtb_bar_not(&mut t);
4848 | ^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'b` must outlive `'static`
4949 |
50note: due to current limitations in the borrow checker, this implies a `'static` lifetime
50note: due to a current limitation of the type system, this implies a `'static` lifetime
5151 --> $DIR/hrtb-perfect-forwarding.rs:37:8
5252 |
5353LL | T: for<'a> Foo<&'a isize> + Bar<&'b isize>,
tests/ui/implied-bounds/normalization-placeholder-leak.fail.stderr+12
......@@ -30,6 +30,12 @@ LL | fn test_lifetime<'lt, T: Trait>(_: Foo<&'lt u8>) {}
3030 | | |
3131 | | lifetime `'lt` defined here
3232 | requires that `'lt` must outlive `'static`
33 |
34note: due to a current limitation of the type system, this implies a `'static` lifetime
35 --> $DIR/normalization-placeholder-leak.rs:19:5
36 |
37LL | for<'x> T::Ty<'x>: Sized;
38 | ^^^^^^^^^^^^^^^^^^^^^^^^
3339
3440error: lifetime may not live long enough
3541 --> $DIR/normalization-placeholder-leak.rs:38:5
......@@ -39,6 +45,12 @@ LL | fn test_alias<'lt, T: AnotherTrait>(_: Foo<T::Ty2::<'lt>>) {}
3945 | | |
4046 | | lifetime `'lt` defined here
4147 | requires that `'lt` must outlive `'static`
48 |
49note: due to a current limitation of the type system, this implies a `'static` lifetime
50 --> $DIR/normalization-placeholder-leak.rs:19:5
51 |
52LL | for<'x> T::Ty<'x>: Sized;
53 | ^^^^^^^^^^^^^^^^^^^^^^^^
4254
4355error: aborting due to 6 previous errors
4456
tests/ui/inference/issue-72616.stderr+4-8
......@@ -6,14 +6,10 @@ LL | if String::from("a") == "a".try_into().unwrap() {}
66 | |
77 | type must be known at this point
88 |
9 = note: cannot satisfy `String: PartialEq<_>`
10 = help: the following types implement trait `PartialEq<Rhs>`:
11 `String` implements `PartialEq<&str>`
12 `String` implements `PartialEq<ByteStr>`
13 `String` implements `PartialEq<ByteString>`
14 `String` implements `PartialEq<Cow<'_, str>>`
15 `String` implements `PartialEq<str>`
16 `String` implements `PartialEq`
9 = note: multiple `impl`s satisfying `String: PartialEq<_>` found in the following crates: `alloc`, `std`:
10 - impl PartialEq for String;
11 - impl PartialEq<Path> for String;
12 - impl PartialEq<PathBuf> for String;
1713help: try using a fully qualified path to specify the expected types
1814 |
1915LL - if String::from("a") == "a".try_into().unwrap() {}
tests/ui/invalid/invalid-no-sanitize.rs deleted-5
......@@ -1,5 +0,0 @@
1#![feature(no_sanitize)]
2
3#[no_sanitize(brontosaurus)] //~ ERROR invalid argument
4fn main() {
5}
tests/ui/invalid/invalid-no-sanitize.stderr deleted-10
......@@ -1,10 +0,0 @@
1error: invalid argument for `no_sanitize`
2 --> $DIR/invalid-no-sanitize.rs:3:15
3 |
4LL | #[no_sanitize(brontosaurus)]
5 | ^^^^^^^^^^^^
6 |
7 = note: expected one of: `address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow-call-stack`, or `thread`
8
9error: aborting due to 1 previous error
10
tests/ui/issues/issue-26217.stderr+3-3
......@@ -6,11 +6,11 @@ LL | fn bar<'a>() {
66LL | foo::<&'a i32>();
77 | ^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
88 |
9note: due to current limitations in the borrow checker, this implies a `'static` lifetime
10 --> $DIR/issue-26217.rs:1:30
9note: due to a current limitation of the type system, this implies a `'static` lifetime
10 --> $DIR/issue-26217.rs:1:19
1111 |
1212LL | fn foo<T>() where for<'a> T: 'a {}
13 | ^^
13 | ^^^^^^^^^^^^^
1414
1515error: aborting due to 1 previous error
1616
tests/ui/lifetimes/issue-105507.fixed+2-2
......@@ -25,8 +25,8 @@ impl<T> ProjectedMyTrait for T
2525 where
2626 T: Project,
2727 for<'a> T::Projected<'a>: MyTrait,
28 //~^ NOTE due to current limitations in the borrow checker, this implies a `'static` lifetime
29 //~| NOTE due to current limitations in the borrow checker, this implies a `'static` lifetime
28 //~^ NOTE due to a current limitation of the type system, this implies a `'static` lifetime
29 //~| NOTE due to a current limitation of the type system, this implies a `'static` lifetime
3030{}
3131
3232fn require_trait<T: MyTrait>(_: T) {}
tests/ui/lifetimes/issue-105507.rs+2-2
......@@ -25,8 +25,8 @@ impl<T> ProjectedMyTrait for T
2525 where
2626 T: Project,
2727 for<'a> T::Projected<'a>: MyTrait,
28 //~^ NOTE due to current limitations in the borrow checker, this implies a `'static` lifetime
29 //~| NOTE due to current limitations in the borrow checker, this implies a `'static` lifetime
28 //~^ NOTE due to a current limitation of the type system, this implies a `'static` lifetime
29 //~| NOTE due to a current limitation of the type system, this implies a `'static` lifetime
3030{}
3131
3232fn require_trait<T: MyTrait>(_: T) {}
tests/ui/lifetimes/issue-105507.stderr+2-2
......@@ -4,7 +4,7 @@ error: `T` does not live long enough
44LL | require_trait(wrap);
55 | ^^^^^^^^^^^^^^^^^^^
66 |
7note: due to current limitations in the borrow checker, this implies a `'static` lifetime
7note: due to a current limitation of the type system, this implies a `'static` lifetime
88 --> $DIR/issue-105507.rs:27:35
99 |
1010LL | for<'a> T::Projected<'a>: MyTrait,
......@@ -20,7 +20,7 @@ error: `U` does not live long enough
2020LL | require_trait(wrap1);
2121 | ^^^^^^^^^^^^^^^^^^^^
2222 |
23note: due to current limitations in the borrow checker, this implies a `'static` lifetime
23note: due to a current limitation of the type system, this implies a `'static` lifetime
2424 --> $DIR/issue-105507.rs:27:35
2525 |
2626LL | for<'a> T::Projected<'a>: MyTrait,
tests/ui/mismatched_types/closure-arg-type-mismatch.stderr+1-1
......@@ -57,7 +57,7 @@ LL | baz(f);
5757 = note: requirement occurs because of a mutable pointer to `&u32`
5858 = note: mutable pointers are invariant over their type parameter
5959 = help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
60note: due to current limitations in the borrow checker, this implies a `'static` lifetime
60note: due to a current limitation of the type system, this implies a `'static` lifetime
6161 --> $DIR/closure-arg-type-mismatch.rs:8:11
6262 |
6363LL | fn baz<F: Fn(*mut &u32)>(_: F) {}
tests/ui/nll/local-outlives-static-via-hrtb.stderr+6-6
......@@ -12,11 +12,11 @@ LL | assert_static_via_hrtb_with_assoc_type(&&local);
1212LL | }
1313 | - `local` dropped here while still borrowed
1414 |
15note: due to current limitations in the borrow checker, this implies a `'static` lifetime
16 --> $DIR/local-outlives-static-via-hrtb.rs:15:53
15note: due to a current limitation of the type system, this implies a `'static` lifetime
16 --> $DIR/local-outlives-static-via-hrtb.rs:15:42
1717 |
1818LL | fn assert_static_via_hrtb<G>(_: G) where for<'a> G: Outlives<'a> {}
19 | ^^^^^^^^^^^^
19 | ^^^^^^^^^^^^^^^^^^^^^^^
2020
2121error[E0597]: `local` does not live long enough
2222 --> $DIR/local-outlives-static-via-hrtb.rs:25:45
......@@ -32,11 +32,11 @@ LL | assert_static_via_hrtb_with_assoc_type(&&local);
3232LL | }
3333 | - `local` dropped here while still borrowed
3434 |
35note: due to current limitations in the borrow checker, this implies a `'static` lifetime
36 --> $DIR/local-outlives-static-via-hrtb.rs:19:20
35note: due to a current limitation of the type system, this implies a `'static` lifetime
36 --> $DIR/local-outlives-static-via-hrtb.rs:19:5
3737 |
3838LL | for<'a> &'a T: Reference<AssociatedType = &'a ()>,
39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
39 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4040
4141error: aborting due to 2 previous errors
4242
tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.nll.stderr+3-3
......@@ -13,11 +13,11 @@ LL | let b = |_| &a;
1313LL | }
1414 | - `a` dropped here while still borrowed
1515 |
16note: due to current limitations in the borrow checker, this implies a `'static` lifetime
17 --> $DIR/location-insensitive-scopes-issue-117146.rs:20:22
16note: due to a current limitation of the type system, this implies a `'static` lifetime
17 --> $DIR/location-insensitive-scopes-issue-117146.rs:20:11
1818 |
1919LL | fn bad<F: Fn(&()) -> &()>(_: F) {}
20 | ^^^
20 | ^^^^^^^^^^^^^^
2121
2222error: implementation of `Fn` is not general enough
2323 --> $DIR/location-insensitive-scopes-issue-117146.rs:13:5
tests/ui/nll/polonius/location-insensitive-scopes-issue-117146.polonius.stderr+3-3
......@@ -13,11 +13,11 @@ LL | let b = |_| &a;
1313LL | }
1414 | - `a` dropped here while still borrowed
1515 |
16note: due to current limitations in the borrow checker, this implies a `'static` lifetime
17 --> $DIR/location-insensitive-scopes-issue-117146.rs:20:22
16note: due to a current limitation of the type system, this implies a `'static` lifetime
17 --> $DIR/location-insensitive-scopes-issue-117146.rs:20:11
1818 |
1919LL | fn bad<F: Fn(&()) -> &()>(_: F) {}
20 | ^^^
20 | ^^^^^^^^^^^^^^
2121
2222error: implementation of `Fn` is not general enough
2323 --> $DIR/location-insensitive-scopes-issue-117146.rs:13:5
tests/ui/nll/type-test-universe.stderr+3-3
......@@ -12,11 +12,11 @@ LL | fn test2<'a>() {
1212LL | outlives_forall::<Value<'a>>();
1313 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ requires that `'a` must outlive `'static`
1414 |
15note: due to current limitations in the borrow checker, this implies a `'static` lifetime
16 --> $DIR/type-test-universe.rs:6:16
15note: due to a current limitation of the type system, this implies a `'static` lifetime
16 --> $DIR/type-test-universe.rs:6:5
1717 |
1818LL | for<'u> T: 'u,
19 | ^^
19 | ^^^^^^^^^^^^^
2020
2121error: aborting due to 2 previous errors
2222
tests/ui/reachable/expr_cast.rs+16-8
......@@ -1,13 +1,21 @@
1#![allow(unused_variables)]
2#![allow(unused_assignments)]
3#![allow(dead_code)]
1//@ check-pass
2//@ edition: 2024
3//
4// Check that we don't warn on `as` casts of never to any as unreachable.
5// While they *are* unreachable, sometimes they are required to appeal typeck.
46#![deny(unreachable_code)]
5#![feature(never_type, type_ascription)]
67
78fn a() {
8 // the cast is unreachable:
9 let x = {return} as !; //~ ERROR unreachable
10 //~| ERROR non-primitive cast
9 _ = {return} as u32;
1110}
1211
13fn main() { }
12fn b() {
13 (return) as u32;
14}
15
16// example that needs an explicit never-to-any `as` cast
17fn example() -> impl Iterator<Item = u8> {
18 todo!() as std::iter::Empty<_>
19}
20
21fn main() {}
tests/ui/reachable/expr_cast.stderr deleted-24
......@@ -1,24 +0,0 @@
1error: unreachable expression
2 --> $DIR/expr_cast.rs:9:13
3 |
4LL | let x = {return} as !;
5 | ^------^^^^^^
6 | ||
7 | |any code following this expression is unreachable
8 | unreachable expression
9 |
10note: the lint level is defined here
11 --> $DIR/expr_cast.rs:4:9
12 |
13LL | #![deny(unreachable_code)]
14 | ^^^^^^^^^^^^^^^^
15
16error[E0605]: non-primitive cast: `()` as `!`
17 --> $DIR/expr_cast.rs:9:13
18 |
19LL | let x = {return} as !;
20 | ^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object
21
22error: aborting due to 2 previous errors
23
24For more information about this error, try `rustc --explain E0605`.
tests/ui/reachable/unreachable-try-pattern.rs+1-1
......@@ -18,7 +18,7 @@ fn bar(x: Result<!, i32>) -> Result<u32, i32> {
1818fn foo(x: Result<!, i32>) -> Result<u32, i32> {
1919 let y = (match x { Ok(n) => Ok(n as u32), Err(e) => Err(e) })?;
2020 //~^ WARN unreachable pattern
21 //~| WARN unreachable expression
21 //~| WARN unreachable call
2222 Ok(y)
2323}
2424
tests/ui/reachable/unreachable-try-pattern.stderr+5-6
......@@ -1,11 +1,10 @@
1warning: unreachable expression
2 --> $DIR/unreachable-try-pattern.rs:19:36
1warning: unreachable call
2 --> $DIR/unreachable-try-pattern.rs:19:33
33 |
44LL | let y = (match x { Ok(n) => Ok(n as u32), Err(e) => Err(e) })?;
5 | -^^^^^^^
6 | |
7 | unreachable expression
8 | any code following this expression is unreachable
5 | ^^ - any code following this expression is unreachable
6 | |
7 | unreachable call
98 |
109note: the lint level is defined here
1110 --> $DIR/unreachable-try-pattern.rs:3:9
tests/ui/resolve/path-attr-in-const-block.stderr+1-1
......@@ -11,7 +11,7 @@ LL | #![path = foo!()]
1111 | ^^^^^^^^^^------^
1212 | | |
1313 | | expected a string literal here
14 | help: must be of the form: `#[path = "file"]`
14 | help: must be of the form: `#![path = "file"]`
1515 |
1616 = note: for more information, visit <https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute>
1717
tests/ui/sanitize-attr/invalid-sanitize.rs created+22
......@@ -0,0 +1,22 @@
1#![feature(sanitize)]
2
3#[sanitize(brontosaurus = "off")] //~ ERROR invalid argument
4fn main() {
5}
6
7#[sanitize(address = "off")] //~ ERROR multiple `sanitize` attributes
8#[sanitize(address = "off")]
9fn multiple_consistent() {}
10
11#[sanitize(address = "on")] //~ ERROR multiple `sanitize` attributes
12#[sanitize(address = "off")]
13fn multiple_inconsistent() {}
14
15#[sanitize(address = "bogus")] //~ ERROR invalid argument for `sanitize`
16fn wrong_value() {}
17
18#[sanitize = "off"] //~ ERROR malformed `sanitize` attribute input
19fn name_value () {}
20
21#[sanitize] //~ ERROR malformed `sanitize` attribute input
22fn just_word() {}
tests/ui/sanitize-attr/invalid-sanitize.stderr created+82
......@@ -0,0 +1,82 @@
1error: malformed `sanitize` attribute input
2 --> $DIR/invalid-sanitize.rs:18:1
3 |
4LL | #[sanitize = "off"]
5 | ^^^^^^^^^^^^^^^^^^^
6 |
7help: the following are the possible correct uses
8 |
9LL - #[sanitize = "off"]
10LL + #[sanitize(address = "on|off")]
11 |
12LL - #[sanitize = "off"]
13LL + #[sanitize(cfi = "on|off")]
14 |
15LL - #[sanitize = "off"]
16LL + #[sanitize(hwaddress = "on|off")]
17 |
18LL - #[sanitize = "off"]
19LL + #[sanitize(kcfi = "on|off")]
20 |
21 = and 5 other candidates
22
23error: malformed `sanitize` attribute input
24 --> $DIR/invalid-sanitize.rs:21:1
25 |
26LL | #[sanitize]
27 | ^^^^^^^^^^^
28 |
29help: the following are the possible correct uses
30 |
31LL | #[sanitize(address = "on|off")]
32 | ++++++++++++++++++++
33LL | #[sanitize(cfi = "on|off")]
34 | ++++++++++++++++
35LL | #[sanitize(hwaddress = "on|off")]
36 | ++++++++++++++++++++++
37LL | #[sanitize(kcfi = "on|off")]
38 | +++++++++++++++++
39 = and 5 other candidates
40
41error: multiple `sanitize` attributes
42 --> $DIR/invalid-sanitize.rs:7:1
43 |
44LL | #[sanitize(address = "off")]
45 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
46 |
47note: attribute also specified here
48 --> $DIR/invalid-sanitize.rs:8:1
49 |
50LL | #[sanitize(address = "off")]
51 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52
53error: multiple `sanitize` attributes
54 --> $DIR/invalid-sanitize.rs:11:1
55 |
56LL | #[sanitize(address = "on")]
57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: remove this attribute
58 |
59note: attribute also specified here
60 --> $DIR/invalid-sanitize.rs:12:1
61 |
62LL | #[sanitize(address = "off")]
63 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64
65error: invalid argument for `sanitize`
66 --> $DIR/invalid-sanitize.rs:3:1
67 |
68LL | #[sanitize(brontosaurus = "off")]
69 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
70 |
71 = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
72
73error: invalid argument for `sanitize`
74 --> $DIR/invalid-sanitize.rs:15:1
75 |
76LL | #[sanitize(address = "bogus")]
77 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
78 |
79 = note: expected one of: `address`, `kernel_address`, `cfi`, `hwaddress`, `kcfi`, `memory`, `memtag`, `shadow_call_stack`, or `thread`
80
81error: aborting due to 6 previous errors
82
tests/ui/sanitize-attr/valid-sanitize.rs created+115
......@@ -0,0 +1,115 @@
1//! Tests where the `#[sanitize(..)]` attribute can and cannot be used.
2
3#![feature(sanitize)]
4#![feature(extern_types)]
5#![feature(impl_trait_in_assoc_type)]
6#![warn(unused_attributes)]
7#![sanitize(address = "off", thread = "on")]
8
9#[sanitize(address = "off", thread = "on")]
10mod submod {}
11
12#[sanitize(address = "off")]
13static FOO: u32 = 0;
14
15#[sanitize(thread = "off")] //~ ERROR sanitize attribute not allowed here
16static BAR: u32 = 0;
17
18#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
19type MyTypeAlias = ();
20
21#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
22trait MyTrait {
23 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
24 const TRAIT_ASSOC_CONST: u32;
25
26 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
27 type TraitAssocType;
28
29 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
30 fn trait_method(&self);
31
32 #[sanitize(address = "off", thread = "on")]
33 fn trait_method_with_default(&self) {}
34
35 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
36 fn trait_assoc_fn();
37}
38
39#[sanitize(address = "off")]
40impl MyTrait for () {
41 const TRAIT_ASSOC_CONST: u32 = 0;
42
43 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
44 type TraitAssocType = Self;
45
46 #[sanitize(address = "off", thread = "on")]
47 fn trait_method(&self) {}
48 #[sanitize(address = "off", thread = "on")]
49 fn trait_method_with_default(&self) {}
50 #[sanitize(address = "off", thread = "on")]
51 fn trait_assoc_fn() {}
52}
53
54trait HasAssocType {
55 type T;
56 fn constrain_assoc_type() -> Self::T;
57}
58
59impl HasAssocType for () {
60 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
61 type T = impl Copy;
62 fn constrain_assoc_type() -> Self::T {}
63}
64
65#[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
66struct MyStruct {
67 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
68 field: u32,
69}
70
71#[sanitize(address = "off", thread = "on")]
72impl MyStruct {
73 #[sanitize(address = "off", thread = "on")]
74 fn method(&self) {}
75 #[sanitize(address = "off", thread = "on")]
76 fn assoc_fn() {}
77}
78
79extern "C" {
80 #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
81 static X: u32;
82
83 #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
84 type T;
85
86 #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
87 fn foreign_fn();
88}
89
90#[sanitize(address = "off", thread = "on")]
91fn main() {
92 #[sanitize(address = "off", thread = "on")] //~ ERROR sanitize attribute not allowed here
93 let _ = ();
94
95 // Currently not allowed on let statements, even if they bind to a closure.
96 // It might be nice to support this as a special case someday, but trying
97 // to define the precise boundaries of that special case might be tricky.
98 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
99 let _let_closure = || ();
100
101 // In situations where attributes can already be applied to expressions,
102 // the sanitize attribute is allowed on closure expressions.
103 let _closure_tail_expr = {
104 #[sanitize(address = "off", thread = "on")]
105 || ()
106 };
107
108 match () {
109 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
110 () => (),
111 }
112
113 #[sanitize(address = "off")] //~ ERROR sanitize attribute not allowed here
114 return ();
115}
tests/ui/sanitize-attr/valid-sanitize.stderr created+190
......@@ -0,0 +1,190 @@
1error: sanitize attribute not allowed here
2 --> $DIR/valid-sanitize.rs:15:1
3 |
4LL | #[sanitize(thread = "off")]
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
6LL | static BAR: u32 = 0;
7 | -------------------- not a function, impl block, or module
8 |
9 = help: sanitize attribute can be applied to a function (with body), impl block, or module
10
11error: sanitize attribute not allowed here
12 --> $DIR/valid-sanitize.rs:18:1
13 |
14LL | #[sanitize(address = "off")]
15 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
16LL | type MyTypeAlias = ();
17 | ---------------------- not a function, impl block, or module
18 |
19 = help: sanitize attribute can be applied to a function (with body), impl block, or module
20
21error: sanitize attribute not allowed here
22 --> $DIR/valid-sanitize.rs:21:1
23 |
24LL | #[sanitize(address = "off")]
25 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
26LL | / trait MyTrait {
27LL | | #[sanitize(address = "off")]
28LL | | const TRAIT_ASSOC_CONST: u32;
29... |
30LL | | fn trait_assoc_fn();
31LL | | }
32 | |_- not a function, impl block, or module
33 |
34 = help: sanitize attribute can be applied to a function (with body), impl block, or module
35
36error: sanitize attribute not allowed here
37 --> $DIR/valid-sanitize.rs:65:1
38 |
39LL | #[sanitize(address = "off")]
40 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
41LL | / struct MyStruct {
42LL | | #[sanitize(address = "off")]
43LL | | field: u32,
44LL | | }
45 | |_- not a function, impl block, or module
46 |
47 = help: sanitize attribute can be applied to a function (with body), impl block, or module
48
49error: sanitize attribute not allowed here
50 --> $DIR/valid-sanitize.rs:67:5
51 |
52LL | #[sanitize(address = "off")]
53 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54LL | field: u32,
55 | ---------- not a function, impl block, or module
56 |
57 = help: sanitize attribute can be applied to a function (with body), impl block, or module
58
59error: sanitize attribute not allowed here
60 --> $DIR/valid-sanitize.rs:92:5
61 |
62LL | #[sanitize(address = "off", thread = "on")]
63 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
64LL | let _ = ();
65 | ----------- not a function, impl block, or module
66 |
67 = help: sanitize attribute can be applied to a function (with body), impl block, or module
68
69error: sanitize attribute not allowed here
70 --> $DIR/valid-sanitize.rs:98:5
71 |
72LL | #[sanitize(address = "off")]
73 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74LL | let _let_closure = || ();
75 | ------------------------- not a function, impl block, or module
76 |
77 = help: sanitize attribute can be applied to a function (with body), impl block, or module
78
79error: sanitize attribute not allowed here
80 --> $DIR/valid-sanitize.rs:109:9
81 |
82LL | #[sanitize(address = "off")]
83 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
84LL | () => (),
85 | -------- not a function, impl block, or module
86 |
87 = help: sanitize attribute can be applied to a function (with body), impl block, or module
88
89error: sanitize attribute not allowed here
90 --> $DIR/valid-sanitize.rs:113:5
91 |
92LL | #[sanitize(address = "off")]
93 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
94LL | return ();
95 | --------- not a function, impl block, or module
96 |
97 = help: sanitize attribute can be applied to a function (with body), impl block, or module
98
99error: sanitize attribute not allowed here
100 --> $DIR/valid-sanitize.rs:23:5
101 |
102LL | #[sanitize(address = "off")]
103 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
104LL | const TRAIT_ASSOC_CONST: u32;
105 | ----------------------------- not a function, impl block, or module
106 |
107 = help: sanitize attribute can be applied to a function (with body), impl block, or module
108
109error: sanitize attribute not allowed here
110 --> $DIR/valid-sanitize.rs:26:5
111 |
112LL | #[sanitize(address = "off")]
113 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114LL | type TraitAssocType;
115 | -------------------- not a function, impl block, or module
116 |
117 = help: sanitize attribute can be applied to a function (with body), impl block, or module
118
119error: sanitize attribute not allowed here
120 --> $DIR/valid-sanitize.rs:29:5
121 |
122LL | #[sanitize(address = "off")]
123 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
124LL | fn trait_method(&self);
125 | ----------------------- function has no body
126 |
127 = help: sanitize attribute can be applied to a function (with body), impl block, or module
128
129error: sanitize attribute not allowed here
130 --> $DIR/valid-sanitize.rs:35:5
131 |
132LL | #[sanitize(address = "off")]
133 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
134LL | fn trait_assoc_fn();
135 | -------------------- function has no body
136 |
137 = help: sanitize attribute can be applied to a function (with body), impl block, or module
138
139error: sanitize attribute not allowed here
140 --> $DIR/valid-sanitize.rs:43:5
141 |
142LL | #[sanitize(address = "off")]
143 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
144LL | type TraitAssocType = Self;
145 | --------------------------- not a function, impl block, or module
146 |
147 = help: sanitize attribute can be applied to a function (with body), impl block, or module
148
149error: sanitize attribute not allowed here
150 --> $DIR/valid-sanitize.rs:60:5
151 |
152LL | #[sanitize(address = "off")]
153 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
154LL | type T = impl Copy;
155 | ------------------- not a function, impl block, or module
156 |
157 = help: sanitize attribute can be applied to a function (with body), impl block, or module
158
159error: sanitize attribute not allowed here
160 --> $DIR/valid-sanitize.rs:80:5
161 |
162LL | #[sanitize(address = "off", thread = "on")]
163 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
164LL | static X: u32;
165 | -------------- not a function, impl block, or module
166 |
167 = help: sanitize attribute can be applied to a function (with body), impl block, or module
168
169error: sanitize attribute not allowed here
170 --> $DIR/valid-sanitize.rs:83:5
171 |
172LL | #[sanitize(address = "off", thread = "on")]
173 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
174LL | type T;
175 | ------- not a function, impl block, or module
176 |
177 = help: sanitize attribute can be applied to a function (with body), impl block, or module
178
179error: sanitize attribute not allowed here
180 --> $DIR/valid-sanitize.rs:86:5
181 |
182LL | #[sanitize(address = "off", thread = "on")]
183 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
184LL | fn foreign_fn();
185 | ---------------- function has no body
186 |
187 = help: sanitize attribute can be applied to a function (with body), impl block, or module
188
189error: aborting due to 18 previous errors
190
tests/ui/sanitizer/inline-always-sanitize.rs created+15
......@@ -0,0 +1,15 @@
1//@ check-pass
2
3#![feature(sanitize)]
4
5#[inline(always)]
6//~^ NOTE inlining requested here
7#[sanitize(address = "off")]
8//~^ WARN setting `sanitize` off will have no effect after inlining
9//~| NOTE on by default
10fn x() {
11}
12
13fn main() {
14 x()
15}
tests/ui/sanitizer/inline-always-sanitize.stderr created+15
......@@ -0,0 +1,15 @@
1warning: setting `sanitize` off will have no effect after inlining
2 --> $DIR/inline-always-sanitize.rs:7:1
3 |
4LL | #[sanitize(address = "off")]
5 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7note: inlining requested here
8 --> $DIR/inline-always-sanitize.rs:5:1
9 |
10LL | #[inline(always)]
11 | ^^^^^^^^^^^^^^^^^
12 = note: `#[warn(inline_no_sanitize)]` on by default
13
14warning: 1 warning emitted
15
tests/ui/sanitizer/inline-always.rs deleted-15
......@@ -1,15 +0,0 @@
1//@ check-pass
2
3#![feature(no_sanitize)]
4
5#[inline(always)]
6//~^ NOTE inlining requested here
7#[no_sanitize(address)]
8//~^ WARN will have no effect after inlining
9//~| NOTE on by default
10fn x() {
11}
12
13fn main() {
14 x()
15}
tests/ui/sanitizer/inline-always.stderr deleted-15
......@@ -1,15 +0,0 @@
1warning: `no_sanitize` will have no effect after inlining
2 --> $DIR/inline-always.rs:7:1
3 |
4LL | #[no_sanitize(address)]
5 | ^^^^^^^^^^^^^^^^^^^^^^^
6 |
7note: inlining requested here
8 --> $DIR/inline-always.rs:5:1
9 |
10LL | #[inline(always)]
11 | ^^^^^^^^^^^^^^^^^
12 = note: `#[warn(inline_no_sanitize)]` on by default
13
14warning: 1 warning emitted
15
tests/ui/suggestions/partialeq_suggest_swap_on_e0277.stderr+2
......@@ -10,6 +10,8 @@ LL | String::from("Girls Band Cry") == T(String::from("Girls Band Cry"));
1010 `String` implements `PartialEq<ByteStr>`
1111 `String` implements `PartialEq<ByteString>`
1212 `String` implements `PartialEq<Cow<'_, str>>`
13 `String` implements `PartialEq<Path>`
14 `String` implements `PartialEq<PathBuf>`
1315 `String` implements `PartialEq<str>`
1416 `String` implements `PartialEq`
1517 = note: `T` implements `PartialEq<String>`
tests/ui/transmutability/references/reject_lifetime_extension.stderr+3-3
......@@ -67,11 +67,11 @@ LL | unsafe { extend_hrtb(src) }
6767 | `src` escapes the function body here
6868 | argument requires that `'a` must outlive `'static`
6969 |
70note: due to current limitations in the borrow checker, this implies a `'static` lifetime
71 --> $DIR/reject_lifetime_extension.rs:85:25
70note: due to a current limitation of the type system, this implies a `'static` lifetime
71 --> $DIR/reject_lifetime_extension.rs:85:9
7272 |
7373LL | for<'b> &'b u8: TransmuteFrom<&'a u8>,
74 | ^^^^^^^^^^^^^^^^^^^^^
74 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7575
7676error: aborting due to 8 previous errors
7777
triagebot.toml+2-2
......@@ -579,7 +579,7 @@ trigger_files = [
579579 "src/doc/unstable-book/src/compiler-flags/sanitizer.md",
580580 "src/doc/unstable-book/src/language-features/cfg-sanitize.md",
581581 "src/doc/unstable-book/src/language-features/cfi-encoding.md",
582 "src/doc/unstable-book/src/language-features/no-sanitize.md",
582 "src/doc/unstable-book/src/language-features/sanitize.md",
583583 "tests/codegen-llvm/sanitizer",
584584 "tests/codegen-llvm/split-lto-unit.rs",
585585 "tests/codegen-llvm/stack-probes-inline.rs",
......@@ -1209,7 +1209,7 @@ cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
12091209[mentions."src/doc/unstable-book/src/language-features/cfi-encoding.md"]
12101210cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
12111211
1212[mentions."src/doc/unstable-book/src/language-features/no-sanitize.md"]
1212[mentions."src/doc/unstable-book/src/language-features/sanitize.md"]
12131213cc = ["@rust-lang/project-exploit-mitigations", "@rcvalle"]
12141214
12151215[mentions."src/doc/rustc/src/check-cfg.md"]