diff --git a/compiler/rustc_expand/src/mbe/diagnostics.rs b/compiler/rustc_expand/src/mbe/diagnostics.rs index f469b2daef5..b1d9cea2773 100644 --- a/compiler/rustc_expand/src/mbe/diagnostics.rs +++ b/compiler/rustc_expand/src/mbe/diagnostics.rs @@ -1,12 +1,10 @@ -use std::borrow::Cow; - use crate::base::{DummyResult, ExtCtxt, MacResult}; use crate::expand::{parse_ast_fragment, AstFragmentKind}; use crate::mbe::{ macro_parser::{MatcherLoc, NamedParseResult, ParseResult::*, TtParser}, macro_rules::{try_match_macro, Tracker}, }; -use rustc_ast::token::{self, Token}; +use rustc_ast::token::{self, Token, TokenKind}; use rustc_ast::tokenstream::TokenStream; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diagnostic, DiagnosticBuilder, DiagnosticMessage}; @@ -14,6 +12,7 @@ use rustc_span::source_map::SourceMap; use rustc_span::symbol::Ident; use rustc_span::Span; +use std::borrow::Cow; use super::macro_rules::{parser_from_cx, NoopTracker}; @@ -63,6 +62,13 @@ pub(super) fn failed_to_match_macro<'cx>( err.note(format!("while trying to match {remaining_matcher}")); } + if let MatcherLoc::Token { token: expected_token } = &remaining_matcher + && (matches!(expected_token.kind, TokenKind::Interpolated(_)) + || matches!(token.kind, TokenKind::Interpolated(_))) + { + err.note("captured metavariables except for `$tt`, `$ident` and `$lifetime` cannot be compared to other tokens"); + } + // Check whether there's a missing comma in this macro call, like `println!("{}" a);` if let Some((arg, comma_span)) = arg.add_comma() { for lhs in lhses { diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index 07fa7c55df6..4110b176b41 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -4,7 +4,6 @@ use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirIdMap; -use rustc_infer::infer; use rustc_infer::infer::{DefiningAnchor, InferCtxt, InferOk, TyCtxtInferExt}; use rustc_middle::ty::visit::TypeVisitableExt; use rustc_middle::ty::{self, Ty, TyCtxt}; @@ -73,40 +72,16 @@ fn deref(&self) -> &Self::Target { } } -/// A temporary returned by `Inherited::build(...)`. This is necessary -/// for multiple `InferCtxt` to share the same `typeck_results` -/// without using `Rc` or something similar. -pub struct InheritedBuilder<'tcx> { - infcx: infer::InferCtxtBuilder<'tcx>, - typeck_results: RefCell>, -} - impl<'tcx> Inherited<'tcx> { - pub fn build(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> InheritedBuilder<'tcx> { + pub fn new(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Self { let hir_owner = tcx.hir().local_def_id_to_hir_id(def_id).owner; - InheritedBuilder { - infcx: tcx - .infer_ctxt() - .ignoring_regions() - .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)), - typeck_results: RefCell::new(ty::TypeckResults::new(hir_owner)), - } - } -} - -impl<'tcx> InheritedBuilder<'tcx> { - pub fn enter(mut self, f: F) -> R - where - F: FnOnce(&Inherited<'tcx>) -> R, - { - f(&Inherited::new(self.infcx.build(), self.typeck_results)) - } -} - -impl<'tcx> Inherited<'tcx> { - fn new(infcx: InferCtxt<'tcx>, typeck_results: RefCell>) -> Self { - let tcx = infcx.tcx; + let infcx = tcx + .infer_ctxt() + .ignoring_regions() + .with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id)) + .build(); + let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner)); Inherited { typeck_results, diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index e397dfd4570..70124a77364 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -45,13 +45,14 @@ mod upvar; mod writeback; -pub use diverges::Diverges; -pub use expectation::Expectation; -pub use fn_ctxt::*; -pub use inherited::{Inherited, InheritedBuilder}; +pub use fn_ctxt::FnCtxt; +pub use inherited::Inherited; use crate::check::check_fn; use crate::coercion::DynamicCoerceMany; +use crate::diverges::Diverges; +use crate::expectation::Expectation; +use crate::fn_ctxt::RawTy; use crate::gather_locals::GatherLocalsVisitor; use rustc_data_structures::unord::UnordSet; use rustc_errors::{ @@ -105,10 +106,9 @@ pub struct LocalTy<'tcx> { /// (notably closures), `typeck_results(def_id)` would wind up /// redirecting to the owning function. fn primary_body_of( - tcx: TyCtxt<'_>, - id: hir::HirId, + node: Node<'_>, ) -> Option<(hir::BodyId, Option<&hir::Ty<'_>>, Option<&hir::FnSig<'_>>)> { - match tcx.hir().get(id) { + match node { Node::Item(item) => match item.kind { hir::ItemKind::Const(ty, body) | hir::ItemKind::Static(ty, _, body) => { Some((body, Some(ty), None)) @@ -142,8 +142,7 @@ fn has_typeck_results(tcx: TyCtxt<'_>, def_id: DefId) -> bool { } if let Some(def_id) = def_id.as_local() { - let id = tcx.hir().local_def_id_to_hir_id(def_id); - primary_body_of(tcx, id).is_some() + primary_body_of(tcx.hir().get_by_def_id(def_id)).is_some() } else { false } @@ -198,143 +197,140 @@ fn typeck_with_fallback<'tcx>( } let id = tcx.hir().local_def_id_to_hir_id(def_id); + let node = tcx.hir().get(id); let span = tcx.hir().span(id); // Figure out what primary body this item has. - let (body_id, body_ty, fn_sig) = primary_body_of(tcx, id).unwrap_or_else(|| { + let (body_id, body_ty, fn_sig) = primary_body_of(node).unwrap_or_else(|| { span_bug!(span, "can't type-check body of {:?}", def_id); }); let body = tcx.hir().body(body_id); - let typeck_results = Inherited::build(tcx, def_id).enter(|inh| { - let param_env = tcx.param_env(def_id); - let param_env = if tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) { - param_env.without_const() + let param_env = tcx.param_env(def_id); + let param_env = if tcx.has_attr(def_id.to_def_id(), sym::rustc_do_not_const_check) { + param_env.without_const() + } else { + param_env + }; + let inh = Inherited::new(tcx, def_id); + let mut fcx = FnCtxt::new(&inh, param_env, def_id); + + if let Some(hir::FnSig { header, decl, .. }) = fn_sig { + let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { + fcx.astconv().ty_of_fn(id, header.unsafety, header.abi, decl, None, None) } else { - param_env + tcx.fn_sig(def_id).subst_identity() }; - let mut fcx = FnCtxt::new(&inh, param_env, def_id); - if let Some(hir::FnSig { header, decl, .. }) = fn_sig { - let fn_sig = if rustc_hir_analysis::collect::get_infer_ret_ty(&decl.output).is_some() { - fcx.astconv().ty_of_fn(id, header.unsafety, header.abi, decl, None, None) - } else { - tcx.fn_sig(def_id).subst_identity() - }; + check_abi(tcx, id, span, fn_sig.abi()); - check_abi(tcx, id, span, fn_sig.abi()); + // Compute the function signature from point of view of inside the fn. + let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); + let fn_sig = fcx.normalize(body.value.span, fn_sig); - // Compute the function signature from point of view of inside the fn. - let fn_sig = tcx.liberate_late_bound_regions(def_id.to_def_id(), fn_sig); - let fn_sig = fcx.normalize(body.value.span, fn_sig); - - check_fn(&mut fcx, fn_sig, decl, def_id, body, None); - } else { - let expected_type = body_ty - .and_then(|ty| match ty.kind { - hir::TyKind::Infer => Some(fcx.astconv().ast_ty_to_ty(ty)), - _ => None, - }) - .unwrap_or_else(|| match tcx.hir().get(id) { - Node::AnonConst(_) => match tcx.hir().get(tcx.hir().parent_id(id)) { - Node::Expr(&hir::Expr { - kind: hir::ExprKind::ConstBlock(ref anon_const), - .. - }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span, - }), - Node::Ty(&hir::Ty { - kind: hir::TyKind::Typeof(ref anon_const), .. - }) if anon_const.hir_id == id => fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::TypeInference, - span, - }), - Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) - | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { - let operand_ty = - asm.operands.iter().find_map(|(op, _op_sp)| match op { - hir::InlineAsmOperand::Const { anon_const } - if anon_const.hir_id == id => - { - // Inline assembly constants must be integers. - Some(fcx.next_int_var()) - } - hir::InlineAsmOperand::SymFn { anon_const } - if anon_const.hir_id == id => - { - Some(fcx.next_ty_var(TypeVariableOrigin { - kind: TypeVariableOriginKind::MiscVariable, - span, - })) - } - _ => None, - }); - operand_ty.unwrap_or_else(fallback) + check_fn(&mut fcx, fn_sig, decl, def_id, body, None); + } else { + let expected_type = if let Some(&hir::Ty { kind: hir::TyKind::Infer, span, .. }) = body_ty { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + })) + } else if let Node::AnonConst(_) = node { + match tcx.hir().get(tcx.hir().parent_id(id)) { + Node::Expr(&hir::Expr { + kind: hir::ExprKind::ConstBlock(ref anon_const), .. + }) if anon_const.hir_id == id => Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + })), + Node::Ty(&hir::Ty { kind: hir::TyKind::Typeof(ref anon_const), .. }) + if anon_const.hir_id == id => + { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::TypeInference, + span, + })) + } + Node::Expr(&hir::Expr { kind: hir::ExprKind::InlineAsm(asm), .. }) + | Node::Item(&hir::Item { kind: hir::ItemKind::GlobalAsm(asm), .. }) => { + asm.operands.iter().find_map(|(op, _op_sp)| match op { + hir::InlineAsmOperand::Const { anon_const } if anon_const.hir_id == id => { + // Inline assembly constants must be integers. + Some(fcx.next_int_var()) } - _ => fallback(), - }, - _ => fallback(), - }); - - let expected_type = fcx.normalize(body.value.span, expected_type); - fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); - - // Gather locals in statics (because of block expressions). - GatherLocalsVisitor::new(&fcx).visit_body(body); - - fcx.check_expr_coercable_to_type(&body.value, expected_type, None); - - fcx.write_ty(id, expected_type); + hir::InlineAsmOperand::SymFn { anon_const } if anon_const.hir_id == id => { + Some(fcx.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span, + })) + } + _ => None, + }) + } + _ => None, + } + } else { + None }; + let expected_type = expected_type.unwrap_or_else(fallback); - fcx.type_inference_fallback(); + let expected_type = fcx.normalize(body.value.span, expected_type); + fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized); - // Even though coercion casts provide type hints, we check casts after fallback for - // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. - fcx.check_casts(); - fcx.select_obligations_where_possible(|_| {}); + // Gather locals in statics (because of block expressions). + GatherLocalsVisitor::new(&fcx).visit_body(body); - // Closure and generator analysis may run after fallback - // because they don't constrain other type variables. - // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) - let prev_constness = fcx.param_env.constness(); - fcx.param_env = fcx.param_env.without_const(); - fcx.closure_analyze(body); - fcx.param_env = fcx.param_env.with_constness(prev_constness); - assert!(fcx.deferred_call_resolutions.borrow().is_empty()); - // Before the generator analysis, temporary scopes shall be marked to provide more - // precise information on types to be captured. - fcx.resolve_rvalue_scopes(def_id.to_def_id()); + fcx.check_expr_coercable_to_type(&body.value, expected_type, None); - for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { - let ty = fcx.normalize(span, ty); - fcx.require_type_is_sized(ty, span, code); - } + fcx.write_ty(id, expected_type); + }; - fcx.select_obligations_where_possible(|_| {}); + fcx.type_inference_fallback(); - debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); + // Even though coercion casts provide type hints, we check casts after fallback for + // backwards compatibility. This makes fallback a stronger type hint than a cast coercion. + fcx.check_casts(); + fcx.select_obligations_where_possible(|_| {}); - // This must be the last thing before `report_ambiguity_errors`. - fcx.resolve_generator_interiors(def_id.to_def_id()); + // Closure and generator analysis may run after fallback + // because they don't constrain other type variables. + // Closure analysis only runs on closures. Therefore they only need to fulfill non-const predicates (as of now) + let prev_constness = fcx.param_env.constness(); + fcx.param_env = fcx.param_env.without_const(); + fcx.closure_analyze(body); + fcx.param_env = fcx.param_env.with_constness(prev_constness); + assert!(fcx.deferred_call_resolutions.borrow().is_empty()); + // Before the generator analysis, temporary scopes shall be marked to provide more + // precise information on types to be captured. + fcx.resolve_rvalue_scopes(def_id.to_def_id()); - debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); + for (ty, span, code) in fcx.deferred_sized_obligations.borrow_mut().drain(..) { + let ty = fcx.normalize(span, ty); + fcx.require_type_is_sized(ty, span, code); + } - if let None = fcx.infcx.tainted_by_errors() { - fcx.report_ambiguity_errors(); - } + fcx.select_obligations_where_possible(|_| {}); - if let None = fcx.infcx.tainted_by_errors() { - fcx.check_transmutes(); - } + debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); - fcx.check_asms(); + // This must be the last thing before `report_ambiguity_errors`. + fcx.resolve_generator_interiors(def_id.to_def_id()); - fcx.infcx.skip_region_resolution(); + debug!(pending_obligations = ?fcx.fulfillment_cx.borrow().pending_obligations()); - fcx.resolve_type_vars_in_body(body) - }); + if let None = fcx.infcx.tainted_by_errors() { + fcx.report_ambiguity_errors(); + } + + if let None = fcx.infcx.tainted_by_errors() { + fcx.check_transmutes(); + } + + fcx.check_asms(); + + fcx.infcx.skip_region_resolution(); + + let typeck_results = fcx.resolve_type_vars_in_body(body); // Consistency check our TypeckResults instance can hold all ItemLocalIds // it will need to hold. diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs index e5d2fb2ea28..043892410ce 100644 --- a/compiler/rustc_interface/src/util.rs +++ b/compiler/rustc_interface/src/util.rs @@ -110,7 +110,7 @@ pub fn create_session( add_configuration(&mut cfg, &mut sess, &*codegen_backend); let mut check_cfg = config::to_crate_check_config(check_cfg); - check_cfg.fill_well_known(); + check_cfg.fill_well_known(&sess.target); sess.parse_sess.config = cfg; sess.parse_sess.check_config = check_cfg; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 5eba208e3ed..f6888e55ad4 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1849,20 +1849,12 @@ fn resolve_rustdoc_path( &mut self, path_str: &str, ns: Namespace, - mut parent_scope: ParentScope<'a>, + parent_scope: ParentScope<'a>, ) -> Option { let mut segments = Vec::from_iter(path_str.split("::").map(Ident::from_str).map(Segment::from_ident)); if let Some(segment) = segments.first_mut() { - if segment.ident.name == kw::Crate { - // FIXME: `resolve_path` always resolves `crate` to the current crate root, but - // rustdoc wants it to resolve to the `parent_scope`'s crate root. This trick of - // replacing `crate` with `self` and changing the current module should achieve - // the same effect. - segment.ident.name = kw::SelfLower; - parent_scope.module = - self.expect_module(parent_scope.module.def_id().krate.as_def_id()); - } else if segment.ident.name == kw::Empty { + if segment.ident.name == kw::Empty { segment.ident.name = kw::PathRoot; } } diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs index d4e4ace889b..485c3f55462 100644 --- a/compiler/rustc_session/src/config.rs +++ b/compiler/rustc_session/src/config.rs @@ -1137,7 +1137,7 @@ fn fill_well_known_names(&mut self) { } /// Fills a `CrateCheckConfig` with well-known configuration values. - fn fill_well_known_values(&mut self) { + fn fill_well_known_values(&mut self, current_target: &Target) { if !self.well_known_values { return; } @@ -1229,6 +1229,7 @@ fn fill_well_known_values(&mut self) { for target in TARGETS .iter() .map(|target| Target::expect_builtin(&TargetTriple::from_triple(target))) + .chain(iter::once(current_target.clone())) { values_target_os.insert(Symbol::intern(&target.options.os)); values_target_family @@ -1243,9 +1244,9 @@ fn fill_well_known_values(&mut self) { } } - pub fn fill_well_known(&mut self) { + pub fn fill_well_known(&mut self, current_target: &Target) { self.fill_well_known_names(); - self.fill_well_known_values(); + self.fill_well_known_values(current_target); } } diff --git a/library/alloc/src/collections/vec_deque/drain.rs b/library/alloc/src/collections/vec_deque/drain.rs index 89feb361ddc..0be274a3822 100644 --- a/library/alloc/src/collections/vec_deque/drain.rs +++ b/library/alloc/src/collections/vec_deque/drain.rs @@ -52,36 +52,22 @@ pub(super) unsafe fn new( } } - // Only returns pointers to the slices, as that's - // all we need to drop them. May only be called if `self.remaining != 0`. + // Only returns pointers to the slices, as that's all we need + // to drop them. May only be called if `self.remaining != 0`. unsafe fn as_slices(&self) -> (*mut [T], *mut [T]) { unsafe { let deque = self.deque.as_ref(); - // FIXME: This is doing almost exactly the same thing as the else branch in `VecDeque::slice_ranges`. - // Unfortunately, we can't just call `slice_ranges` here, as the deque's `len` is currently - // just `drain_start`, so the range check would (almost) always panic. Between temporarily - // adjusting the deques `len` to call `slice_ranges`, and just copy pasting the `slice_ranges` - // implementation, this seemed like the less hacky solution, though it might be good to - // find a better one in the future. - // because `self.remaining != 0`, we know that `self.idx < deque.original_len`, so it's a valid - // logical index. - let wrapped_start = deque.to_physical_idx(self.idx); + // We know that `self.idx + self.remaining <= deque.len <= usize::MAX`, so this won't overflow. + let logical_remaining_range = self.idx..self.idx + self.remaining; - let head_len = deque.capacity() - wrapped_start; - - let (a_range, b_range) = if head_len >= self.remaining { - (wrapped_start..wrapped_start + self.remaining, 0..0) - } else { - let tail_len = self.remaining - head_len; - (wrapped_start..deque.capacity(), 0..tail_len) - }; - - // SAFETY: the range `self.idx..self.idx+self.remaining` lies strictly inside - // the range `0..deque.original_len`. because of this, and because of the fact - // that we acquire `a_range` and `b_range` exactly like `slice_ranges` would, - // it's guaranteed that `a_range` and `b_range` represent valid ranges into - // the deques buffer. + // SAFETY: `logical_remaining_range` represents the + // range into the logical buffer of elements that + // haven't been drained yet, so they're all initialized, + // and `slice::range(start..end, end) == start..end`, + // so the preconditions for `slice_ranges` are met. + let (a_range, b_range) = + deque.slice_ranges(logical_remaining_range.clone(), logical_remaining_range.end); (deque.buffer_range(a_range), deque.buffer_range(b_range)) } } diff --git a/library/alloc/src/collections/vec_deque/mod.rs b/library/alloc/src/collections/vec_deque/mod.rs index 8317ac431a5..48e907e402c 100644 --- a/library/alloc/src/collections/vec_deque/mod.rs +++ b/library/alloc/src/collections/vec_deque/mod.rs @@ -1156,7 +1156,7 @@ pub fn iter_mut(&mut self) -> IterMut<'_, T> { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_slices(&self) -> (&[T], &[T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&*self.buffer_range(a_range), &*self.buffer_range(b_range)) } @@ -1190,7 +1190,7 @@ pub fn as_slices(&self) -> (&[T], &[T]) { #[inline] #[stable(feature = "deque_extras_15", since = "1.5.0")] pub fn as_mut_slices(&mut self) -> (&mut [T], &mut [T]) { - let (a_range, b_range) = self.slice_ranges(..); + let (a_range, b_range) = self.slice_ranges(.., self.len); // SAFETY: `slice_ranges` always returns valid ranges into // the physical buffer. unsafe { (&mut *self.buffer_range(a_range), &mut *self.buffer_range(b_range)) } @@ -1232,19 +1232,28 @@ pub fn is_empty(&self) -> bool { /// Given a range into the logical buffer of the deque, this function /// return two ranges into the physical buffer that correspond to - /// the given range. - fn slice_ranges(&self, range: R) -> (Range, Range) + /// the given range. The `len` parameter should usually just be `self.len`; + /// the reason it's passed explicitly is that if the deque is wrapped in + /// a `Drain`, then `self.len` is not actually the length of the deque. + /// + /// # Safety + /// + /// This function is always safe to call. For the resulting ranges to be valid + /// ranges into the physical buffer, the caller must ensure that the result of + /// calling `slice::range(range, ..len)` represents a valid range into the + /// logical buffer, and that all elements in that range are initialized. + fn slice_ranges(&self, range: R, len: usize) -> (Range, Range) where R: RangeBounds, { - let Range { start, end } = slice::range(range, ..self.len); + let Range { start, end } = slice::range(range, ..len); let len = end - start; if len == 0 { (0..0, 0..0) } else { - // `slice::range` guarantees that `start <= end <= self.len`. - // because `len != 0`, we know that `start < end`, so `start < self.len` + // `slice::range` guarantees that `start <= end <= len`. + // because `len != 0`, we know that `start < end`, so `start < len` // and the indexing is valid. let wrapped_start = self.to_physical_idx(start); @@ -1290,7 +1299,7 @@ pub fn range(&self, range: R) -> Iter<'_, T> where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and @@ -1330,7 +1339,7 @@ pub fn range_mut(&mut self, range: R) -> IterMut<'_, T> where R: RangeBounds, { - let (a_range, b_range) = self.slice_ranges(range); + let (a_range, b_range) = self.slice_ranges(range, self.len); // SAFETY: The ranges returned by `slice_ranges` // are valid ranges into the physical buffer, so // it's ok to pass them to `buffer_range` and diff --git a/library/std/src/sys/common/mod.rs b/library/std/src/sys/common/mod.rs index 29fc0835d76..2b8782ddf44 100644 --- a/library/std/src/sys/common/mod.rs +++ b/library/std/src/sys/common/mod.rs @@ -12,6 +12,7 @@ pub mod alloc; pub mod small_c_string; +pub mod thread_local; #[cfg(test)] mod tests; diff --git a/library/std/src/sys/common/thread_local/fast_local.rs b/library/std/src/sys/common/thread_local/fast_local.rs new file mode 100644 index 00000000000..2addcc4a759 --- /dev/null +++ b/library/std/src/sys/common/thread_local/fast_local.rs @@ -0,0 +1,276 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + // If the platform has support for `#[thread_local]`, use it. + #[thread_local] + static mut VAL: $t = INIT_EXPR; + + // If a dtor isn't needed we can do something "very raw" and + // just get going. + if !$crate::mem::needs_drop::<$t>() { + unsafe { + return $crate::option::Option::Some(&VAL) + } + } + + // 0 == dtor not registered + // 1 == dtor registered, dtor not run + // 2 == dtor registered and is running or has run + #[thread_local] + static mut STATE: $crate::primitive::u8 = 0; + + unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { + let ptr = ptr as *mut $t; + + unsafe { + $crate::debug_assert_eq!(STATE, 1); + STATE = 2; + $crate::ptr::drop_in_place(ptr); + } + } + + unsafe { + match STATE { + // 0 == we haven't registered a destructor, so do + // so now. + 0 => { + $crate::thread::__LocalKeyInner::<$t>::register_dtor( + $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, + destroy, + ); + STATE = 1; + $crate::option::Option::Some(&VAL) + } + // 1 == the destructor is registered and the value + // is valid, so return the pointer. + 1 => $crate::option::Option::Some(&VAL), + // otherwise the destructor has already run, so we + // can't give access. + _ => $crate::option::Option::None, + } + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + #[thread_local] + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::<$t>::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +pub mod fast { + use super::super::lazy::LazyKeyInner; + use crate::cell::Cell; + use crate::sys::thread_local_dtor::register_dtor; + use crate::{fmt, mem, panic}; + + #[derive(Copy, Clone)] + enum DtorState { + Unregistered, + Registered, + RunningOrHasRun, + } + + // This data structure has been carefully constructed so that the fast path + // only contains one branch on x86. That optimization is necessary to avoid + // duplicated tls lookups on OSX. + // + // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 + pub struct Key { + // If `LazyKeyInner::get` returns `None`, that indicates either: + // * The value has never been initialized + // * The value is being recursively initialized + // * The value has already been destroyed or is being destroyed + // To determine which kind of `None`, check `dtor_state`. + // + // This is very optimizer friendly for the fast path - initialized but + // not yet dropped. + inner: LazyKeyInner, + + // Metadata to keep track of the state of the destructor. Remember that + // this variable is thread-local, not global. + dtor_state: Cell, + } + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + impl Key { + pub const fn new() -> Key { + Key { inner: LazyKeyInner::new(), dtor_state: Cell::new(DtorState::Unregistered) } + } + + // note that this is just a publicly-callable function only for the + // const-initialized form of thread locals, basically a way to call the + // free `register_dtor` function defined elsewhere in std. + pub unsafe fn register_dtor(a: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { + unsafe { + register_dtor(a, dtor); + } + } + + pub unsafe fn get T>(&self, init: F) -> Option<&'static T> { + // SAFETY: See the definitions of `LazyKeyInner::get` and + // `try_initialize` for more information. + // + // The caller must ensure no mutable references are ever active to + // the inner cell or the inner T when this is called. + // The `try_initialize` is dependant on the passed `init` function + // for this. + unsafe { + match self.inner.get() { + Some(val) => Some(val), + None => self.try_initialize(init), + } + } + } + + // `try_initialize` is only called once per fast thread local variable, + // except in corner cases where thread_local dtors reference other + // thread_local's, or it is being recursively initialized. + // + // Macos: Inlining this function can cause two `tlv_get_addr` calls to + // be performed for every call to `Key::get`. + // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 + #[inline(never)] + unsafe fn try_initialize T>(&self, init: F) -> Option<&'static T> { + // SAFETY: See comment above (this function doc). + if !mem::needs_drop::() || unsafe { self.try_register_dtor() } { + // SAFETY: See comment above (this function doc). + Some(unsafe { self.inner.initialize(init) }) + } else { + None + } + } + + // `try_register_dtor` is only called once per fast thread local + // variable, except in corner cases where thread_local dtors reference + // other thread_local's, or it is being recursively initialized. + unsafe fn try_register_dtor(&self) -> bool { + match self.dtor_state.get() { + DtorState::Unregistered => { + // SAFETY: dtor registration happens before initialization. + // Passing `self` as a pointer while using `destroy_value` + // is safe because the function will build a pointer to a + // Key, which is the type of self and so find the correct + // size. + unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::) }; + self.dtor_state.set(DtorState::Registered); + true + } + DtorState::Registered => { + // recursively initialized + true + } + DtorState::RunningOrHasRun => false, + } + } + } + + unsafe extern "C" fn destroy_value(ptr: *mut u8) { + let ptr = ptr as *mut Key; + + // SAFETY: + // + // The pointer `ptr` has been built just above and comes from + // `try_register_dtor` where it is originally a Key coming from `self`, + // making it non-NUL and of the correct type. + // + // Right before we run the user destructor be sure to set the + // `Option` to `None`, and `dtor_state` to `RunningOrHasRun`. This + // causes future calls to `get` to run `try_initialize_drop` again, + // which will now fail, and return `None`. + // + // Wrap the call in a catch to ensure unwinding is caught in the event + // a panic takes place in a destructor. + if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { + let value = (*ptr).inner.take(); + (*ptr).dtor_state.set(DtorState::RunningOrHasRun); + drop(value); + })) { + rtabort!("thread local panicked on drop"); + } + } +} diff --git a/library/std/src/sys/common/thread_local/mod.rs b/library/std/src/sys/common/thread_local/mod.rs new file mode 100644 index 00000000000..1fee84a0434 --- /dev/null +++ b/library/std/src/sys/common/thread_local/mod.rs @@ -0,0 +1,109 @@ +//! The following module declarations are outside cfg_if because the internal +//! `__thread_local_internal` macro does not seem to be exported properly when using cfg_if +#![unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] + +#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] +mod fast_local; +#[cfg(all( + not(target_thread_local), + not(all(target_family = "wasm", not(target_feature = "atomics"))) +))] +mod os_local; +#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] +mod static_local; + +#[cfg(not(test))] +cfg_if::cfg_if! { + if #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] { + #[doc(hidden)] + pub use static_local::statik::Key; + } else if #[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics")))))] { + #[doc(hidden)] + pub use fast_local::fast::Key; + } else if #[cfg(all(not(target_thread_local), not(all(target_family = "wasm", not(target_feature = "atomics")))))] { + #[doc(hidden)] + pub use os_local::os::Key; + } +} + +#[doc(hidden)] +#[cfg(test)] +pub use realstd::thread::__LocalKeyInner as Key; + +mod lazy { + use crate::cell::UnsafeCell; + use crate::hint; + use crate::mem; + + pub struct LazyKeyInner { + inner: UnsafeCell>, + } + + impl LazyKeyInner { + pub const fn new() -> LazyKeyInner { + LazyKeyInner { inner: UnsafeCell::new(None) } + } + + pub unsafe fn get(&self) -> Option<&'static T> { + // SAFETY: The caller must ensure no reference is ever handed out to + // the inner cell nor mutable reference to the Option inside said + // cell. This make it safe to hand a reference, though the lifetime + // of 'static is itself unsafe, making the get method unsafe. + unsafe { (*self.inner.get()).as_ref() } + } + + /// The caller must ensure that no reference is active: this method + /// needs unique access. + pub unsafe fn initialize T>(&self, init: F) -> &'static T { + // Execute the initialization up front, *then* move it into our slot, + // just in case initialization fails. + let value = init(); + let ptr = self.inner.get(); + + // SAFETY: + // + // note that this can in theory just be `*ptr = Some(value)`, but due to + // the compiler will currently codegen that pattern with something like: + // + // ptr::drop_in_place(ptr) + // ptr::write(ptr, Some(value)) + // + // Due to this pattern it's possible for the destructor of the value in + // `ptr` (e.g., if this is being recursively initialized) to re-access + // TLS, in which case there will be a `&` and `&mut` pointer to the same + // value (an aliasing violation). To avoid setting the "I'm running a + // destructor" flag we just use `mem::replace` which should sequence the + // operations a little differently and make this safe to call. + // + // The precondition also ensures that we are the only one accessing + // `self` at the moment so replacing is fine. + unsafe { + let _ = mem::replace(&mut *ptr, Some(value)); + } + + // SAFETY: With the call to `mem::replace` it is guaranteed there is + // a `Some` behind `ptr`, not a `None` so `unreachable_unchecked` + // will never be reached. + unsafe { + // After storing `Some` we want to get a reference to the contents of + // what we just stored. While we could use `unwrap` here and it should + // always work it empirically doesn't seem to always get optimized away, + // which means that using something like `try_with` can pull in + // panicking code and cause a large size bloat. + match *ptr { + Some(ref x) => x, + None => hint::unreachable_unchecked(), + } + } + } + + /// The other methods hand out references while taking &self. + /// As such, callers of this method must ensure no `&` and `&mut` are + /// available and used at the same time. + #[allow(unused)] + pub unsafe fn take(&mut self) -> Option { + // SAFETY: See doc comment for this method. + unsafe { (*self.inner.get()).take() } + } + } +} diff --git a/library/std/src/sys/common/thread_local/os_local.rs b/library/std/src/sys/common/thread_local/os_local.rs new file mode 100644 index 00000000000..6f6560c4aa9 --- /dev/null +++ b/library/std/src/sys/common/thread_local/os_local.rs @@ -0,0 +1,217 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[cfg_attr(not(windows), inline)] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // On platforms without `#[thread_local]` we fall back to the + // same implementation as below for os thread locals. + #[inline] + const fn __init() -> $t { INIT_EXPR } + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = _init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing initial value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + + // When reading this function you might ask "why is this inlined + // everywhere other than Windows?", and that's a very reasonable + // question to ask. The short story is that it segfaults rustc if + // this function is inlined. The longer story is that Windows looks + // to not support `extern` references to thread locals across DLL + // boundaries. This appears to at least not be supported in the ABI + // that LLVM implements. + // + // Because of this we never inline on Windows, but we do inline on + // other platforms (where external references to thread locals + // across DLLs are supported). A better fix for this would be to + // inline this function on Windows, but only for "statically linked" + // components. For example if two separately compiled rlibs end up + // getting linked into a DLL then it's fine to inline this function + // across that boundary. It's only not fine to inline this function + // across a DLL boundary. Unfortunately rustc doesn't currently + // have this sort of logic available in an attribute, and it's not + // clear that rustc is even equipped to answer this (it's more of a + // Cargo question kinda). This means that, unfortunately, Windows + // gets the pessimistic path for now where it's never inlined. + // + // The issue of "should enable on Windows sometimes" is #84933 + #[cfg_attr(not(windows), inline)] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +#[doc(hidden)] +pub mod os { + use super::super::lazy::LazyKeyInner; + use crate::cell::Cell; + use crate::sys_common::thread_local_key::StaticKey as OsStaticKey; + use crate::{fmt, marker, panic, ptr}; + + /// Use a regular global static to store this key; the state provided will then be + /// thread-local. + pub struct Key { + // OS-TLS key that we'll use to key off. + os: OsStaticKey, + marker: marker::PhantomData>, + } + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + unsafe impl Sync for Key {} + + struct Value { + inner: LazyKeyInner, + key: &'static Key, + } + + impl Key { + #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")] + pub const fn new() -> Key { + Key { os: OsStaticKey::new(Some(destroy_value::)), marker: marker::PhantomData } + } + + /// It is a requirement for the caller to ensure that no mutable + /// reference is active when this method is called. + pub unsafe fn get(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: See the documentation for this method. + let ptr = unsafe { self.os.get() as *mut Value }; + if ptr.addr() > 1 { + // SAFETY: the check ensured the pointer is safe (its destructor + // is not running) + it is coming from a trusted source (self). + if let Some(ref value) = unsafe { (*ptr).inner.get() } { + return Some(value); + } + } + // SAFETY: At this point we are sure we have no value and so + // initializing (or trying to) is safe. + unsafe { self.try_initialize(init) } + } + + // `try_initialize` is only called once per os thread local variable, + // except in corner cases where thread_local dtors reference other + // thread_local's, or it is being recursively initialized. + unsafe fn try_initialize(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: No mutable references are ever handed out meaning getting + // the value is ok. + let ptr = unsafe { self.os.get() as *mut Value }; + if ptr.addr() == 1 { + // destructor is running + return None; + } + + let ptr = if ptr.is_null() { + // If the lookup returned null, we haven't initialized our own + // local copy, so do that now. + let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self })); + // SAFETY: At this point we are sure there is no value inside + // ptr so setting it will not affect anyone else. + unsafe { + self.os.set(ptr as *mut u8); + } + ptr + } else { + // recursive initialization + ptr + }; + + // SAFETY: ptr has been ensured as non-NUL just above an so can be + // dereferenced safely. + unsafe { Some((*ptr).inner.initialize(init)) } + } + } + + unsafe extern "C" fn destroy_value(ptr: *mut u8) { + // SAFETY: + // + // The OS TLS ensures that this key contains a null value when this + // destructor starts to run. We set it back to a sentinel value of 1 to + // ensure that any future calls to `get` for this thread will return + // `None`. + // + // Note that to prevent an infinite loop we reset it back to null right + // before we return from the destructor ourselves. + // + // Wrap the call in a catch to ensure unwinding is caught in the event + // a panic takes place in a destructor. + if let Err(_) = panic::catch_unwind(|| unsafe { + let ptr = Box::from_raw(ptr as *mut Value); + let key = ptr.key; + key.os.set(ptr::invalid_mut(1)); + drop(ptr); + key.os.set(ptr::null_mut()); + }) { + rtabort!("thread local panicked on drop"); + } + } +} diff --git a/library/std/src/sys/common/thread_local/static_local.rs b/library/std/src/sys/common/thread_local/static_local.rs new file mode 100644 index 00000000000..ec4f2a12b7e --- /dev/null +++ b/library/std/src/sys/common/thread_local/static_local.rs @@ -0,0 +1,115 @@ +#[doc(hidden)] +#[macro_export] +#[allow_internal_unstable( + thread_local_internals, + cfg_target_thread_local, + thread_local, + libstd_thread_internals +)] +#[allow_internal_unsafe] +macro_rules! __thread_local_inner { + // used to generate the `LocalKey` value for const-initialized thread locals + (@key $t:ty, const $init:expr) => {{ + #[inline] // see comments below + #[deny(unsafe_op_in_unsafe_fn)] + unsafe fn __getit( + _init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + const INIT_EXPR: $t = $init; + + // wasm without atomics maps directly to `static mut`, and dtors + // aren't implemented because thread dtors aren't really a thing + // on wasm right now + // + // FIXME(#84224) this should come after the `target_thread_local` + // block. + static mut VAL: $t = INIT_EXPR; + unsafe { $crate::option::Option::Some(&VAL) } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + }}; + + // used to generate the `LocalKey` value for `thread_local!` + (@key $t:ty, $init:expr) => { + { + #[inline] + fn __init() -> $t { $init } + #[inline] + unsafe fn __getit( + init: $crate::option::Option<&mut $crate::option::Option<$t>>, + ) -> $crate::option::Option<&'static $t> { + static __KEY: $crate::thread::__LocalKeyInner<$t> = + $crate::thread::__LocalKeyInner::new(); + + // FIXME: remove the #[allow(...)] marker when macros don't + // raise warning for missing/extraneous unsafe blocks anymore. + // See https://github.com/rust-lang/rust/issues/74838. + #[allow(unused_unsafe)] + unsafe { + __KEY.get(move || { + if let $crate::option::Option::Some(init) = init { + if let $crate::option::Option::Some(value) = init.take() { + return value; + } else if $crate::cfg!(debug_assertions) { + $crate::unreachable!("missing default value"); + } + } + __init() + }) + } + } + + unsafe { + $crate::thread::LocalKey::new(__getit) + } + } + }; + ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { + $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = + $crate::__thread_local_inner!(@key $t, $($init)*); + } +} + +/// On some targets like wasm there's no threads, so no need to generate +/// thread locals and we can instead just use plain statics! +#[doc(hidden)] +pub mod statik { + use super::super::lazy::LazyKeyInner; + use crate::fmt; + + pub struct Key { + inner: LazyKeyInner, + } + + unsafe impl Sync for Key {} + + impl fmt::Debug for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Key").finish_non_exhaustive() + } + } + + impl Key { + pub const fn new() -> Key { + Key { inner: LazyKeyInner::new() } + } + + pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> { + // SAFETY: The caller must ensure no reference is ever handed out to + // the inner cell nor mutable reference to the Option inside said + // cell. This make it safe to hand a reference, though the lifetime + // of 'static is itself unsafe, making the get method unsafe. + let value = unsafe { + match self.inner.get() { + Some(ref value) => value, + None => self.inner.initialize(init), + } + }; + + Some(value) + } + } +} diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index cf7c2e05a2e..7fdf03acc14 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -173,200 +173,6 @@ macro_rules! thread_local { ); } -#[doc(hidden)] -#[unstable(feature = "thread_local_internals", reason = "should not be necessary", issue = "none")] -#[macro_export] -#[allow_internal_unstable(thread_local_internals, cfg_target_thread_local, thread_local)] -#[allow_internal_unsafe] -macro_rules! __thread_local_inner { - // used to generate the `LocalKey` value for const-initialized thread locals - (@key $t:ty, const $init:expr) => {{ - #[cfg_attr(not(windows), inline)] // see comments below - #[deny(unsafe_op_in_unsafe_fn)] - unsafe fn __getit( - _init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - const INIT_EXPR: $t = $init; - - // wasm without atomics maps directly to `static mut`, and dtors - // aren't implemented because thread dtors aren't really a thing - // on wasm right now - // - // FIXME(#84224) this should come after the `target_thread_local` - // block. - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - { - static mut VAL: $t = INIT_EXPR; - unsafe { $crate::option::Option::Some(&VAL) } - } - - // If the platform has support for `#[thread_local]`, use it. - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[thread_local] - static mut VAL: $t = INIT_EXPR; - - // If a dtor isn't needed we can do something "very raw" and - // just get going. - if !$crate::mem::needs_drop::<$t>() { - unsafe { - return $crate::option::Option::Some(&VAL) - } - } - - // 0 == dtor not registered - // 1 == dtor registered, dtor not run - // 2 == dtor registered and is running or has run - #[thread_local] - static mut STATE: $crate::primitive::u8 = 0; - - unsafe extern "C" fn destroy(ptr: *mut $crate::primitive::u8) { - let ptr = ptr as *mut $t; - - unsafe { - $crate::debug_assert_eq!(STATE, 1); - STATE = 2; - $crate::ptr::drop_in_place(ptr); - } - } - - unsafe { - match STATE { - // 0 == we haven't registered a destructor, so do - // so now. - 0 => { - $crate::thread::__FastLocalKeyInner::<$t>::register_dtor( - $crate::ptr::addr_of_mut!(VAL) as *mut $crate::primitive::u8, - destroy, - ); - STATE = 1; - $crate::option::Option::Some(&VAL) - } - // 1 == the destructor is registered and the value - // is valid, so return the pointer. - 1 => $crate::option::Option::Some(&VAL), - // otherwise the destructor has already run, so we - // can't give access. - _ => $crate::option::Option::None, - } - } - } - - // On platforms without `#[thread_local]` we fall back to the - // same implementation as below for os thread locals. - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - { - #[inline] - const fn __init() -> $t { INIT_EXPR } - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = _init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing initial value"); - } - } - __init() - }) - } - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - }}; - - // used to generate the `LocalKey` value for `thread_local!` - (@key $t:ty, $init:expr) => { - { - #[inline] - fn __init() -> $t { $init } - - // When reading this function you might ask "why is this inlined - // everywhere other than Windows?", and that's a very reasonable - // question to ask. The short story is that it segfaults rustc if - // this function is inlined. The longer story is that Windows looks - // to not support `extern` references to thread locals across DLL - // boundaries. This appears to at least not be supported in the ABI - // that LLVM implements. - // - // Because of this we never inline on Windows, but we do inline on - // other platforms (where external references to thread locals - // across DLLs are supported). A better fix for this would be to - // inline this function on Windows, but only for "statically linked" - // components. For example if two separately compiled rlibs end up - // getting linked into a DLL then it's fine to inline this function - // across that boundary. It's only not fine to inline this function - // across a DLL boundary. Unfortunately rustc doesn't currently - // have this sort of logic available in an attribute, and it's not - // clear that rustc is even equipped to answer this (it's more of a - // Cargo question kinda). This means that, unfortunately, Windows - // gets the pessimistic path for now where it's never inlined. - // - // The issue of "should enable on Windows sometimes" is #84933 - #[cfg_attr(not(windows), inline)] - unsafe fn __getit( - init: $crate::option::Option<&mut $crate::option::Option<$t>>, - ) -> $crate::option::Option<&'static $t> { - #[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] - static __KEY: $crate::thread::__StaticLocalKeyInner<$t> = - $crate::thread::__StaticLocalKeyInner::new(); - - #[thread_local] - #[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__FastLocalKeyInner<$t> = - $crate::thread::__FastLocalKeyInner::new(); - - #[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), - ))] - static __KEY: $crate::thread::__OsLocalKeyInner<$t> = - $crate::thread::__OsLocalKeyInner::new(); - - // FIXME: remove the #[allow(...)] marker when macros don't - // raise warning for missing/extraneous unsafe blocks anymore. - // See https://github.com/rust-lang/rust/issues/74838. - #[allow(unused_unsafe)] - unsafe { - __KEY.get(move || { - if let $crate::option::Option::Some(init) = init { - if let $crate::option::Option::Some(value) = init.take() { - return value; - } else if $crate::cfg!(debug_assertions) { - $crate::unreachable!("missing default value"); - } - } - __init() - }) - } - } - - unsafe { - $crate::thread::LocalKey::new(__getit) - } - } - }; - ($(#[$attr:meta])* $vis:vis $name:ident, $t:ty, $($init:tt)*) => { - $(#[$attr])* $vis const $name: $crate::thread::LocalKey<$t> = - $crate::__thread_local_inner!(@key $t, $($init)*); - } -} - /// An error returned by [`LocalKey::try_with`](struct.LocalKey.html#method.try_with). #[stable(feature = "thread_local_try_with", since = "1.26.0")] #[non_exhaustive] @@ -779,376 +585,3 @@ pub fn replace(&'static self, value: T) -> T { self.with(|cell| cell.replace(value)) } } - -mod lazy { - use crate::cell::UnsafeCell; - use crate::hint; - use crate::mem; - - pub struct LazyKeyInner { - inner: UnsafeCell>, - } - - impl LazyKeyInner { - pub const fn new() -> LazyKeyInner { - LazyKeyInner { inner: UnsafeCell::new(None) } - } - - pub unsafe fn get(&self) -> Option<&'static T> { - // SAFETY: The caller must ensure no reference is ever handed out to - // the inner cell nor mutable reference to the Option inside said - // cell. This make it safe to hand a reference, though the lifetime - // of 'static is itself unsafe, making the get method unsafe. - unsafe { (*self.inner.get()).as_ref() } - } - - /// The caller must ensure that no reference is active: this method - /// needs unique access. - pub unsafe fn initialize T>(&self, init: F) -> &'static T { - // Execute the initialization up front, *then* move it into our slot, - // just in case initialization fails. - let value = init(); - let ptr = self.inner.get(); - - // SAFETY: - // - // note that this can in theory just be `*ptr = Some(value)`, but due to - // the compiler will currently codegen that pattern with something like: - // - // ptr::drop_in_place(ptr) - // ptr::write(ptr, Some(value)) - // - // Due to this pattern it's possible for the destructor of the value in - // `ptr` (e.g., if this is being recursively initialized) to re-access - // TLS, in which case there will be a `&` and `&mut` pointer to the same - // value (an aliasing violation). To avoid setting the "I'm running a - // destructor" flag we just use `mem::replace` which should sequence the - // operations a little differently and make this safe to call. - // - // The precondition also ensures that we are the only one accessing - // `self` at the moment so replacing is fine. - unsafe { - let _ = mem::replace(&mut *ptr, Some(value)); - } - - // SAFETY: With the call to `mem::replace` it is guaranteed there is - // a `Some` behind `ptr`, not a `None` so `unreachable_unchecked` - // will never be reached. - unsafe { - // After storing `Some` we want to get a reference to the contents of - // what we just stored. While we could use `unwrap` here and it should - // always work it empirically doesn't seem to always get optimized away, - // which means that using something like `try_with` can pull in - // panicking code and cause a large size bloat. - match *ptr { - Some(ref x) => x, - None => hint::unreachable_unchecked(), - } - } - } - - /// The other methods hand out references while taking &self. - /// As such, callers of this method must ensure no `&` and `&mut` are - /// available and used at the same time. - #[allow(unused)] - pub unsafe fn take(&mut self) -> Option { - // SAFETY: See doc comment for this method. - unsafe { (*self.inner.get()).take() } - } - } -} - -/// On some targets like wasm there's no threads, so no need to generate -/// thread locals and we can instead just use plain statics! -#[doc(hidden)] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] -pub mod statik { - use super::lazy::LazyKeyInner; - use crate::fmt; - - pub struct Key { - inner: LazyKeyInner, - } - - unsafe impl Sync for Key {} - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - impl Key { - pub const fn new() -> Key { - Key { inner: LazyKeyInner::new() } - } - - pub unsafe fn get(&self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: The caller must ensure no reference is ever handed out to - // the inner cell nor mutable reference to the Option inside said - // cell. This make it safe to hand a reference, though the lifetime - // of 'static is itself unsafe, making the get method unsafe. - let value = unsafe { - match self.inner.get() { - Some(ref value) => value, - None => self.inner.initialize(init), - } - }; - - Some(value) - } - } -} - -#[doc(hidden)] -#[cfg(all(target_thread_local, not(all(target_family = "wasm", not(target_feature = "atomics"))),))] -pub mod fast { - use super::lazy::LazyKeyInner; - use crate::cell::Cell; - use crate::sys::thread_local_dtor::register_dtor; - use crate::{fmt, mem, panic}; - - #[derive(Copy, Clone)] - enum DtorState { - Unregistered, - Registered, - RunningOrHasRun, - } - - // This data structure has been carefully constructed so that the fast path - // only contains one branch on x86. That optimization is necessary to avoid - // duplicated tls lookups on OSX. - // - // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 - pub struct Key { - // If `LazyKeyInner::get` returns `None`, that indicates either: - // * The value has never been initialized - // * The value is being recursively initialized - // * The value has already been destroyed or is being destroyed - // To determine which kind of `None`, check `dtor_state`. - // - // This is very optimizer friendly for the fast path - initialized but - // not yet dropped. - inner: LazyKeyInner, - - // Metadata to keep track of the state of the destructor. Remember that - // this variable is thread-local, not global. - dtor_state: Cell, - } - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - impl Key { - pub const fn new() -> Key { - Key { inner: LazyKeyInner::new(), dtor_state: Cell::new(DtorState::Unregistered) } - } - - // note that this is just a publicly-callable function only for the - // const-initialized form of thread locals, basically a way to call the - // free `register_dtor` function defined elsewhere in std. - pub unsafe fn register_dtor(a: *mut u8, dtor: unsafe extern "C" fn(*mut u8)) { - unsafe { - register_dtor(a, dtor); - } - } - - pub unsafe fn get T>(&self, init: F) -> Option<&'static T> { - // SAFETY: See the definitions of `LazyKeyInner::get` and - // `try_initialize` for more information. - // - // The caller must ensure no mutable references are ever active to - // the inner cell or the inner T when this is called. - // The `try_initialize` is dependant on the passed `init` function - // for this. - unsafe { - match self.inner.get() { - Some(val) => Some(val), - None => self.try_initialize(init), - } - } - } - - // `try_initialize` is only called once per fast thread local variable, - // except in corner cases where thread_local dtors reference other - // thread_local's, or it is being recursively initialized. - // - // Macos: Inlining this function can cause two `tlv_get_addr` calls to - // be performed for every call to `Key::get`. - // LLVM issue: https://bugs.llvm.org/show_bug.cgi?id=41722 - #[inline(never)] - unsafe fn try_initialize T>(&self, init: F) -> Option<&'static T> { - // SAFETY: See comment above (this function doc). - if !mem::needs_drop::() || unsafe { self.try_register_dtor() } { - // SAFETY: See comment above (this function doc). - Some(unsafe { self.inner.initialize(init) }) - } else { - None - } - } - - // `try_register_dtor` is only called once per fast thread local - // variable, except in corner cases where thread_local dtors reference - // other thread_local's, or it is being recursively initialized. - unsafe fn try_register_dtor(&self) -> bool { - match self.dtor_state.get() { - DtorState::Unregistered => { - // SAFETY: dtor registration happens before initialization. - // Passing `self` as a pointer while using `destroy_value` - // is safe because the function will build a pointer to a - // Key, which is the type of self and so find the correct - // size. - unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::) }; - self.dtor_state.set(DtorState::Registered); - true - } - DtorState::Registered => { - // recursively initialized - true - } - DtorState::RunningOrHasRun => false, - } - } - } - - unsafe extern "C" fn destroy_value(ptr: *mut u8) { - let ptr = ptr as *mut Key; - - // SAFETY: - // - // The pointer `ptr` has been built just above and comes from - // `try_register_dtor` where it is originally a Key coming from `self`, - // making it non-NUL and of the correct type. - // - // Right before we run the user destructor be sure to set the - // `Option` to `None`, and `dtor_state` to `RunningOrHasRun`. This - // causes future calls to `get` to run `try_initialize_drop` again, - // which will now fail, and return `None`. - // - // Wrap the call in a catch to ensure unwinding is caught in the event - // a panic takes place in a destructor. - if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| unsafe { - let value = (*ptr).inner.take(); - (*ptr).dtor_state.set(DtorState::RunningOrHasRun); - drop(value); - })) { - rtabort!("thread local panicked on drop"); - } - } -} - -#[doc(hidden)] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub mod os { - use super::lazy::LazyKeyInner; - use crate::cell::Cell; - use crate::sys_common::thread_local_key::StaticKey as OsStaticKey; - use crate::{fmt, marker, panic, ptr}; - - /// Use a regular global static to store this key; the state provided will then be - /// thread-local. - pub struct Key { - // OS-TLS key that we'll use to key off. - os: OsStaticKey, - marker: marker::PhantomData>, - } - - impl fmt::Debug for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Key").finish_non_exhaustive() - } - } - - unsafe impl Sync for Key {} - - struct Value { - inner: LazyKeyInner, - key: &'static Key, - } - - impl Key { - #[rustc_const_unstable(feature = "thread_local_internals", issue = "none")] - pub const fn new() -> Key { - Key { os: OsStaticKey::new(Some(destroy_value::)), marker: marker::PhantomData } - } - - /// It is a requirement for the caller to ensure that no mutable - /// reference is active when this method is called. - pub unsafe fn get(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: See the documentation for this method. - let ptr = unsafe { self.os.get() as *mut Value }; - if ptr.addr() > 1 { - // SAFETY: the check ensured the pointer is safe (its destructor - // is not running) + it is coming from a trusted source (self). - if let Some(ref value) = unsafe { (*ptr).inner.get() } { - return Some(value); - } - } - // SAFETY: At this point we are sure we have no value and so - // initializing (or trying to) is safe. - unsafe { self.try_initialize(init) } - } - - // `try_initialize` is only called once per os thread local variable, - // except in corner cases where thread_local dtors reference other - // thread_local's, or it is being recursively initialized. - unsafe fn try_initialize(&'static self, init: impl FnOnce() -> T) -> Option<&'static T> { - // SAFETY: No mutable references are ever handed out meaning getting - // the value is ok. - let ptr = unsafe { self.os.get() as *mut Value }; - if ptr.addr() == 1 { - // destructor is running - return None; - } - - let ptr = if ptr.is_null() { - // If the lookup returned null, we haven't initialized our own - // local copy, so do that now. - let ptr = Box::into_raw(Box::new(Value { inner: LazyKeyInner::new(), key: self })); - // SAFETY: At this point we are sure there is no value inside - // ptr so setting it will not affect anyone else. - unsafe { - self.os.set(ptr as *mut u8); - } - ptr - } else { - // recursive initialization - ptr - }; - - // SAFETY: ptr has been ensured as non-NUL just above an so can be - // dereferenced safely. - unsafe { Some((*ptr).inner.initialize(init)) } - } - } - - unsafe extern "C" fn destroy_value(ptr: *mut u8) { - // SAFETY: - // - // The OS TLS ensures that this key contains a null value when this - // destructor starts to run. We set it back to a sentinel value of 1 to - // ensure that any future calls to `get` for this thread will return - // `None`. - // - // Note that to prevent an infinite loop we reset it back to null right - // before we return from the destructor ourselves. - // - // Wrap the call in a catch to ensure unwinding is caught in the event - // a panic takes place in a destructor. - if let Err(_) = panic::catch_unwind(|| unsafe { - let ptr = Box::from_raw(ptr as *mut Value); - let key = ptr.key; - key.os.set(ptr::invalid_mut(1)); - drop(ptr); - key.os.set(ptr::null_mut()); - }) { - rtabort!("thread local panicked on drop"); - } - } -} diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 489af776798..b9aaf5f6e15 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -203,44 +203,9 @@ // by the elf linker. "static" is for single-threaded platforms where a global // static is sufficient. -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(not(test))] -#[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] #[doc(hidden)] -pub use self::local::fast::Key as __FastLocalKeyInner; -// when building for tests, use real std's type #[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(test)] -#[cfg(all( - target_thread_local, - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub use realstd::thread::__FastLocalKeyInner; - -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(not(test))] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -#[doc(hidden)] -pub use self::local::os::Key as __OsLocalKeyInner; -// when building for tests, use real std's type -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(test)] -#[cfg(all( - not(target_thread_local), - not(all(target_family = "wasm", not(target_feature = "atomics"))), -))] -pub use realstd::thread::__OsLocalKeyInner; - -#[unstable(feature = "libstd_thread_internals", issue = "none")] -#[cfg(all(target_family = "wasm", not(target_feature = "atomics")))] -#[doc(hidden)] -pub use self::local::statik::Key as __StaticLocalKeyInner; +pub use crate::sys::common::thread_local::Key as __LocalKeyInner; //////////////////////////////////////////////////////////////////////////////// // Builder diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index 8dbfaf4bbc9..0295de8437e 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -346,6 +346,7 @@ fn fold_item(&mut self, item: clean::Item) -> Option { self.cache, ), aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(self.tcx), }); } } diff --git a/src/librustdoc/html/render/context.rs b/src/librustdoc/html/render/context.rs index ed1eb66b97c..63cd0e04a28 100644 --- a/src/librustdoc/html/render/context.rs +++ b/src/librustdoc/html/render/context.rs @@ -17,10 +17,11 @@ use super::search_index::build_index; use super::write_shared::write_shared; use super::{ - collect_spans_and_sources, print_sidebar, scrape_examples_help, sidebar_module_like, AllTypes, - LinkFromSrc, StylePath, + collect_spans_and_sources, scrape_examples_help, + sidebar::print_sidebar, + sidebar::{sidebar_module_like, Sidebar}, + AllTypes, LinkFromSrc, StylePath, }; - use crate::clean::{self, types::ExternalLocation, ExternalCrate}; use crate::config::{ModuleSorting, RenderOptions}; use crate::docfs::{DocFS, PathError}; @@ -35,6 +36,7 @@ use crate::html::{layout, sources, static_files}; use crate::scrape_examples::AllCallLocations; use crate::try_err; +use askama::Template; /// Major driving force in all rustdoc rendering. This contains information /// about where in the tree-like hierarchy rendering is occurring and controls @@ -600,15 +602,18 @@ fn after_krate(&mut self) -> Result<(), Error> { }; let all = shared.all.replace(AllTypes::new()); let mut sidebar = Buffer::html(); - write!(sidebar, "

Crate {}

", crate_name); - let mut items = Buffer::html(); - sidebar_module_like(&mut items, all.item_sections()); - if !items.is_empty() { - sidebar.push_str("
"); - sidebar.push_buffer(items); - sidebar.push_str("
"); - } + let blocks = sidebar_module_like(all.item_sections()); + let bar = Sidebar { + title_prefix: "Crate ", + title: crate_name.as_str(), + is_crate: false, + version: "", + blocks: vec![blocks], + path: String::new(), + }; + + bar.render_into(&mut sidebar).unwrap(); let v = layout::render( &shared.layout, diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index e6a040d02e5..da1f1cf5ecc 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -30,6 +30,7 @@ mod context; mod print_item; +mod sidebar; mod span_map; mod write_shared; @@ -46,14 +47,13 @@ use std::str; use std::string::ToString; +use askama::Template; use rustc_ast_pretty::pprust; use rustc_attr::{ConstStability, Deprecation, StabilityLevel}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; -use rustc_hir::def::CtorKind; use rustc_hir::def_id::{DefId, DefIdSet}; use rustc_hir::Mutability; use rustc_middle::middle::stability; -use rustc_middle::ty; use rustc_middle::ty::TyCtxt; use rustc_span::{ symbol::{sym, Symbol}, @@ -104,6 +104,7 @@ pub(crate) struct IndexItem { pub(crate) parent_idx: Option, pub(crate) search_type: Option, pub(crate) aliases: Box<[Symbol]>, + pub(crate) deprecation: Option, } /// A type used for the search index. @@ -417,7 +418,7 @@ fn document( if let Some(ref name) = item.name { info!("Documenting {}", name); } - document_item_info(w, cx, item, parent); + document_item_info(cx, item, parent).render_into(w).unwrap(); if parent.is_none() { document_full_collapsible(w, item, cx, heading_offset); } else { @@ -459,7 +460,7 @@ fn document_short( parent: &clean::Item, show_def_docs: bool, ) { - document_item_info(w, cx, item, Some(parent)); + document_item_info(cx, item, Some(parent)).render_into(w).unwrap(); if !show_def_docs { return; } @@ -531,25 +532,23 @@ fn document_full_inner( } } +#[derive(Template)] +#[template(path = "item_info.html")] +struct ItemInfo { + items: Vec, +} /// Add extra information about an item such as: /// /// * Stability /// * Deprecated /// * Required features (through the `doc_cfg` feature) fn document_item_info( - w: &mut Buffer, cx: &mut Context<'_>, item: &clean::Item, parent: Option<&clean::Item>, -) { - let item_infos = short_item_info(item, cx, parent); - if !item_infos.is_empty() { - w.write_str(""); - for info in item_infos { - w.write_str(&info); - } - w.write_str(""); - } +) -> ItemInfo { + let items = short_item_info(item, cx, parent); + ItemInfo { items } } fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option { @@ -567,7 +566,25 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option{}", cfg?.render_long_html())) + Some(cfg?.render_long_html()) +} + +#[derive(Template)] +#[template(path = "short_item_info.html")] +enum ShortItemInfo { + /// A message describing the deprecation of this item + Deprecation { + message: String, + }, + /// The feature corresponding to an unstable item, and optionally + /// a tracking issue URL and number. + Unstable { + feature: String, + tracking: Option<(String, u32)>, + }, + Portability { + message: String, + }, } /// Render the stability, deprecation and portability information that is displayed at the top of @@ -576,7 +593,7 @@ fn short_item_info( item: &clean::Item, cx: &mut Context<'_>, parent: Option<&clean::Item>, -) -> Vec { +) -> Vec { let mut extra_info = vec![]; if let Some(depr @ Deprecation { note, since, is_since_rustc_version: _, suggestion: _ }) = @@ -602,15 +619,10 @@ fn short_item_info( if let Some(note) = note { let note = note.as_str(); let html = MarkdownItemInfo(note, &mut cx.id_map); - message.push_str(&format!(": {}", html.into_string())); + message.push_str(": "); + message.push_str(&html.into_string()); } - extra_info.push(format!( - "
\ - 👎\ - {}\ -
", - message, - )); + extra_info.push(ShortItemInfo::Deprecation { message }); } // Render unstable items. But don't render "rustc_private" crates (internal compiler crates). @@ -621,26 +633,17 @@ fn short_item_info( .filter(|stab| stab.feature != sym::rustc_private) .map(|stab| (stab.level, stab.feature)) { - let mut message = "🔬\ - This is a nightly-only experimental API." - .to_owned(); - - let mut feature = format!("{}", Escape(feature.as_str())); - if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) { - feature.push_str(&format!( - " #{issue}", - url = url, - issue = issue - )); - } - - message.push_str(&format!(" ({})", feature)); - - extra_info.push(format!("
{}
", message)); + let tracking = if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) + { + Some((url.clone(), issue.get())) + } else { + None + }; + extra_info.push(ShortItemInfo::Unstable { feature: feature.to_string(), tracking }); } - if let Some(portability) = portability(item, parent) { - extra_info.push(portability); + if let Some(message) = portability(item, parent) { + extra_info.push(ShortItemInfo::Portability { message }); } extra_info @@ -1472,7 +1475,9 @@ fn doc_impl_item( // We need the stability of the item from the trait // because impls can't have a stability. if item.doc_value().is_some() { - document_item_info(&mut info_buffer, cx, it, Some(parent)); + document_item_info(cx, it, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); short_documented = false; } else { @@ -1489,7 +1494,9 @@ fn doc_impl_item( } } } else { - document_item_info(&mut info_buffer, cx, item, Some(parent)); + document_item_info(cx, item, Some(parent)) + .render_into(&mut info_buffer) + .unwrap(); if rendering_params.show_def_docs { document_full(&mut doc_buffer, item, cx, HeadingOffset::H5); short_documented = false; @@ -1862,161 +1869,17 @@ pub(crate) fn render_impl_summary( let is_trait = inner_impl.trait_.is_some(); if is_trait { if let Some(portability) = portability(&i.impl_item, Some(parent)) { - write!(w, "{}", portability); + write!( + w, + "
{}
", + portability + ); } } w.write_str(""); } -fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { - if it.is_struct() - || it.is_trait() - || it.is_primitive() - || it.is_union() - || it.is_enum() - || it.is_mod() - || it.is_typedef() - { - write!( - buffer, - "

{}{}

", - match *it.kind { - clean::ModuleItem(..) => - if it.is_crate() { - "Crate " - } else { - "Module " - }, - _ => "", - }, - it.name.as_ref().unwrap() - ); - } - - buffer.write_str("
"); - if it.is_crate() { - write!(buffer, "
    "); - if let Some(ref version) = cx.cache().crate_version { - write!(buffer, "
  • Version {}
  • ", Escape(version)); - } - write!(buffer, "
  • All Items
  • "); - buffer.write_str("
"); - } - - match *it.kind { - clean::StructItem(ref s) => sidebar_struct(cx, buffer, it, s), - clean::TraitItem(ref t) => sidebar_trait(cx, buffer, it, t), - clean::PrimitiveItem(_) => sidebar_primitive(cx, buffer, it), - clean::UnionItem(ref u) => sidebar_union(cx, buffer, it, u), - clean::EnumItem(ref e) => sidebar_enum(cx, buffer, it, e), - clean::TypedefItem(_) => sidebar_typedef(cx, buffer, it), - clean::ModuleItem(ref m) => sidebar_module(buffer, &m.items), - clean::ForeignTypeItem => sidebar_foreign_type(cx, buffer, it), - _ => {} - } - - // The sidebar is designed to display sibling functions, modules and - // other miscellaneous information. since there are lots of sibling - // items (and that causes quadratic growth in large modules), - // we refactor common parts into a shared JavaScript file per module. - // still, we don't move everything into JS because we want to preserve - // as much HTML as possible in order to allow non-JS-enabled browsers - // to navigate the documentation (though slightly inefficiently). - - if !it.is_mod() { - let path: String = cx.current.iter().map(|s| s.as_str()).intersperse("::").collect(); - - write!(buffer, "

In {}

", path); - } - - // Closes sidebar-elems div. - buffer.write_str("
"); -} - -fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { - if used_links.insert(url.clone()) { - return url; - } - let mut add = 1; - while !used_links.insert(format!("{}-{}", url, add)) { - add += 1; - } - format!("{}-{}", url, add) -} - -struct SidebarLink { - name: Symbol, - url: String, -} - -impl fmt::Display for SidebarLink { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.url, self.name) - } -} - -impl PartialEq for SidebarLink { - fn eq(&self, other: &Self) -> bool { - self.url == other.url - } -} - -impl Eq for SidebarLink {} - -impl PartialOrd for SidebarLink { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for SidebarLink { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.url.cmp(&other.url) - } -} - -fn get_methods( - i: &clean::Impl, - for_deref: bool, - used_links: &mut FxHashSet, - deref_mut: bool, - tcx: TyCtxt<'_>, -) -> Vec { - i.items - .iter() - .filter_map(|item| match item.name { - Some(name) if !name.is_empty() && item.is_method() => { - if !for_deref || should_render_item(item, deref_mut, tcx) { - Some(SidebarLink { - name, - url: get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), - }) - } else { - None - } - } - _ => None, - }) - .collect::>() -} - -fn get_associated_constants( - i: &clean::Impl, - used_links: &mut FxHashSet, -) -> Vec { - i.items - .iter() - .filter_map(|item| match item.name { - Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink { - name, - url: get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), - }), - _ => None, - }) - .collect::>() -} - pub(crate) fn small_url_encode(s: String) -> String { // These characters don't need to be escaped in a URI. // See https://url.spec.whatwg.org/#query-percent-encode-set @@ -2082,232 +1945,6 @@ fn dont_escape(c: u8) -> bool { } } -pub(crate) fn sidebar_render_assoc_items( - cx: &Context<'_>, - out: &mut Buffer, - id_map: &mut IdMap, - concrete: Vec<&Impl>, - synthetic: Vec<&Impl>, - blanket_impl: Vec<&Impl>, -) { - let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| { - let mut links = FxHashSet::default(); - - let mut ret = impls - .iter() - .filter_map(|it| { - let trait_ = it.inner_impl().trait_.as_ref()?; - let encoded = - id_map.derive(get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); - - let i_display = format!("{:#}", trait_.print(cx)); - let out = Escape(&i_display); - let prefix = match it.inner_impl().polarity { - ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", - ty::ImplPolarity::Negative => "!", - }; - let generated = format!("{}{}", encoded, prefix, out); - if links.insert(generated.clone()) { Some(generated) } else { None } - }) - .collect::>(); - ret.sort(); - ret - }; - - let concrete_format = format_impls(concrete, id_map); - let synthetic_format = format_impls(synthetic, id_map); - let blanket_format = format_impls(blanket_impl, id_map); - - if !concrete_format.is_empty() { - print_sidebar_block( - out, - "trait-implementations", - "Trait Implementations", - concrete_format.iter(), - ); - } - - if !synthetic_format.is_empty() { - print_sidebar_block( - out, - "synthetic-implementations", - "Auto Trait Implementations", - synthetic_format.iter(), - ); - } - - if !blanket_format.is_empty() { - print_sidebar_block( - out, - "blanket-implementations", - "Blanket Implementations", - blanket_format.iter(), - ); - } -} - -fn sidebar_assoc_items(cx: &Context<'_>, out: &mut Buffer, it: &clean::Item) { - let did = it.item_id.expect_def_id(); - let cache = cx.cache(); - - if let Some(v) = cache.impls.get(&did) { - let mut used_links = FxHashSet::default(); - let mut id_map = IdMap::new(); - - { - let used_links_bor = &mut used_links; - let mut assoc_consts = v - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)) - .collect::>(); - if !assoc_consts.is_empty() { - // We want links' order to be reproducible so we don't use unstable sort. - assoc_consts.sort(); - - print_sidebar_block( - out, - "implementations", - "Associated Constants", - assoc_consts.iter(), - ); - } - let mut methods = v - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())) - .collect::>(); - if !methods.is_empty() { - // We want links' order to be reproducible so we don't use unstable sort. - methods.sort(); - - print_sidebar_block(out, "implementations", "Methods", methods.iter()); - } - } - - if v.iter().any(|i| i.inner_impl().trait_.is_some()) { - if let Some(impl_) = - v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait()) - { - let mut derefs = DefIdSet::default(); - derefs.insert(did); - sidebar_deref_methods(cx, out, impl_, v, &mut derefs, &mut used_links); - } - - let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = - v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); - let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = - concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); - - sidebar_render_assoc_items(cx, out, &mut id_map, concrete, synthetic, blanket_impl); - } - } -} - -fn sidebar_deref_methods( - cx: &Context<'_>, - out: &mut Buffer, - impl_: &Impl, - v: &[Impl], - derefs: &mut DefIdSet, - used_links: &mut FxHashSet, -) { - let c = cx.cache(); - - debug!("found Deref: {:?}", impl_); - if let Some((target, real_target)) = - impl_.inner_impl().items.iter().find_map(|item| match *item.kind { - clean::AssocTypeItem(box ref t, _) => Some(match *t { - clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), - _ => (&t.type_, &t.type_), - }), - _ => None, - }) - { - debug!("found target, real_target: {:?} {:?}", target, real_target); - if let Some(did) = target.def_id(c) && - let Some(type_did) = impl_.inner_impl().for_.def_id(c) && - // `impl Deref for S` - (did == type_did || !derefs.insert(did)) - { - // Avoid infinite cycles - return; - } - let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); - let inner_impl = target - .def_id(c) - .or_else(|| { - target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) - }) - .and_then(|did| c.impls.get(&did)); - if let Some(impls) = inner_impl { - debug!("found inner_impl: {:?}", impls); - let mut ret = impls - .iter() - .filter(|i| i.inner_impl().trait_.is_none()) - .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx())) - .collect::>(); - if !ret.is_empty() { - let id = if let Some(target_def_id) = real_target.def_id(c) { - cx.deref_id_map.get(&target_def_id).expect("Deref section without derived id") - } else { - "deref-methods" - }; - let title = format!( - "Methods from {}<Target={}>", - Escape(&format!("{:#}", impl_.inner_impl().trait_.as_ref().unwrap().print(cx))), - Escape(&format!("{:#}", real_target.print(cx))), - ); - // We want links' order to be reproducible so we don't use unstable sort. - ret.sort(); - print_sidebar_block(out, id, &title, ret.iter()); - } - } - - // Recurse into any further impls that might exist for `target` - if let Some(target_did) = target.def_id(c) && - let Some(target_impls) = c.impls.get(&target_did) && - let Some(target_deref_impl) = target_impls.iter().find(|i| { - i.inner_impl() - .trait_ - .as_ref() - .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait()) - .unwrap_or(false) - }) - { - sidebar_deref_methods( - cx, - out, - target_deref_impl, - target_impls, - derefs, - used_links, - ); - } - } -} - -fn sidebar_struct(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, s: &clean::Struct) { - let mut sidebar = Buffer::new(); - let fields = get_struct_fields_name(&s.fields); - - if !fields.is_empty() { - match s.ctor_kind { - None => { - print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); - } - Some(CtorKind::Fn) => print_sidebar_title(&mut sidebar, "fields", "Tuple Fields"), - Some(CtorKind::Const) => {} - } - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - fn get_id_for_impl(for_: &clean::Type, trait_: Option<&clean::Path>, cx: &Context<'_>) -> String { match trait_ { Some(t) => small_url_encode(format!("impl-{:#}-for-{:#}", t.print(cx), for_.print(cx))), @@ -2328,131 +1965,6 @@ fn extract_for_impl_name(item: &clean::Item, cx: &Context<'_>) -> Option<(String } } -fn print_sidebar_title(buf: &mut Buffer, id: &str, title: &str) { - write!(buf, "

{}

", id, title); -} - -fn print_sidebar_block( - buf: &mut Buffer, - id: &str, - title: &str, - items: impl Iterator, -) { - print_sidebar_title(buf, id, title); - buf.push_str("
    "); - for item in items { - write!(buf, "
  • {}
  • ", item); - } - buf.push_str("
"); -} - -fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) { - buf.write_str("
"); - - fn print_sidebar_section( - out: &mut Buffer, - items: &[clean::Item], - id: &str, - title: &str, - filter: impl Fn(&clean::Item) -> bool, - mapper: impl Fn(&str) -> String, - ) { - let mut items: Vec<&str> = items - .iter() - .filter_map(|m| match m.name { - Some(ref name) if filter(m) => Some(name.as_str()), - _ => None, - }) - .collect::>(); - - if !items.is_empty() { - items.sort_unstable(); - print_sidebar_block(out, id, title, items.into_iter().map(mapper)); - } - } - - print_sidebar_section( - buf, - &t.items, - "required-associated-types", - "Required Associated Types", - |m| m.is_ty_associated_type(), - |sym| format!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-types", - "Provided Associated Types", - |m| m.is_associated_type(), - |sym| format!("{0}", sym, ItemType::AssocType), - ); - - print_sidebar_section( - buf, - &t.items, - "required-associated-consts", - "Required Associated Constants", - |m| m.is_ty_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-associated-consts", - "Provided Associated Constants", - |m| m.is_associated_const(), - |sym| format!("{0}", sym, ItemType::AssocConst), - ); - - print_sidebar_section( - buf, - &t.items, - "required-methods", - "Required Methods", - |m| m.is_ty_method(), - |sym| format!("{0}", sym, ItemType::TyMethod), - ); - - print_sidebar_section( - buf, - &t.items, - "provided-methods", - "Provided Methods", - |m| m.is_method(), - |sym| format!("{0}", sym, ItemType::Method), - ); - - if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) { - let mut res = implementors - .iter() - .filter(|i| !i.is_on_local_type(cx)) - .filter_map(|i| extract_for_impl_name(&i.impl_item, cx)) - .collect::>(); - - if !res.is_empty() { - res.sort(); - print_sidebar_block( - buf, - "foreign-impls", - "Implementations on Foreign Types", - res.iter().map(|(name, id)| format!("{}", id, Escape(name))), - ); - } - } - - sidebar_assoc_items(cx, buf, it); - - print_sidebar_title(buf, "implementors", "Implementors"); - if t.is_auto(cx.tcx()) { - print_sidebar_title(buf, "synthetic-implementors", "Auto Implementors"); - } - - buf.push_str("
") -} - /// Returns the list of implementations for the primitive reference type, filtering out any /// implementations that are on concrete or partially generic types, only keeping implementations /// of the form `impl Trait for &T`. @@ -2483,89 +1995,6 @@ pub(crate) fn get_filtered_impls_for_reference<'a>( (concrete, synthetic, blanket_impl) } -fn sidebar_primitive(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - - if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { - sidebar_assoc_items(cx, &mut sidebar, it); - } else { - let shared = Rc::clone(&cx.shared); - let (concrete, synthetic, blanket_impl) = get_filtered_impls_for_reference(&shared, it); - - sidebar_render_assoc_items( - cx, - &mut sidebar, - &mut IdMap::new(), - concrete, - synthetic, - blanket_impl, - ); - } - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn sidebar_typedef(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn get_struct_fields_name(fields: &[clean::Item]) -> Vec { - let mut fields = fields - .iter() - .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) - .filter_map(|f| { - f.name.map(|name| format!("{name}", name = name)) - }) - .collect::>(); - fields.sort(); - fields -} - -fn sidebar_union(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, u: &clean::Union) { - let mut sidebar = Buffer::new(); - let fields = get_struct_fields_name(&u.fields); - - if !fields.is_empty() { - print_sidebar_block(&mut sidebar, "fields", "Fields", fields.iter()); - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - -fn sidebar_enum(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, e: &clean::Enum) { - let mut sidebar = Buffer::new(); - - let mut variants = e - .variants() - .filter_map(|v| { - v.name - .as_ref() - .map(|name| format!("{name}", name = name)) - }) - .collect::>(); - if !variants.is_empty() { - variants.sort_unstable(); - print_sidebar_block(&mut sidebar, "variants", "Variants", variants.iter()); - } - - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
{}
", sidebar.into_inner()); - } -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) enum ItemSection { Reexports, @@ -2719,54 +2148,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { } } -pub(crate) fn sidebar_module_like(buf: &mut Buffer, item_sections_in_use: FxHashSet) { - use std::fmt::Write as _; - - let mut sidebar = String::new(); - - for &sec in ItemSection::ALL.iter().filter(|sec| item_sections_in_use.contains(sec)) { - let _ = write!(sidebar, "
  • {}
  • ", sec.id(), sec.name()); - } - - if !sidebar.is_empty() { - write!( - buf, - "
    \ -
      {}
    \ -
    ", - sidebar - ); - } -} - -fn sidebar_module(buf: &mut Buffer, items: &[clean::Item]) { - let item_sections_in_use: FxHashSet<_> = items - .iter() - .filter(|it| { - !it.is_stripped() - && it - .name - .or_else(|| { - if let clean::ImportItem(ref i) = *it.kind && - let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None } - }) - .is_some() - }) - .map(|it| item_ty_to_section(it.type_())) - .collect(); - - sidebar_module_like(buf, item_sections_in_use); -} - -fn sidebar_foreign_type(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item) { - let mut sidebar = Buffer::new(); - sidebar_assoc_items(cx, &mut sidebar, it); - - if !sidebar.is_empty() { - write!(buf, "
    {}
    ", sidebar.into_inner()); - } -} - /// Returns a list of all paths used in the type. /// This is used to help deduplicate imported impls /// for reexported types. If any of the contained diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 08796f10d92..577497868f6 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -470,10 +470,11 @@ fn tag_html(class: &str, title: &str, contents: &str) -> String { // The trailing space after each tag is to space it properly against the rest of the docs. if let Some(depr) = &item.deprecation(tcx) { - let mut message = "Deprecated"; - if !stability::deprecation_in_effect(depr) { - message = "Deprecation planned"; - } + let message = if stability::deprecation_in_effect(depr) { + "Deprecated" + } else { + "Deprecation planned" + }; tags += &tag_html("deprecated", "", message); } diff --git a/src/librustdoc/html/render/search_index.rs b/src/librustdoc/html/render/search_index.rs index e22ac6ec19b..146221f5806 100644 --- a/src/librustdoc/html/render/search_index.rs +++ b/src/librustdoc/html/render/search_index.rs @@ -40,6 +40,7 @@ pub(crate) fn build_index<'tcx>( parent_idx: None, search_type: get_function_type_for_search(item, tcx, impl_generics.as_ref(), cache), aliases: item.attrs.get_doc_aliases(), + deprecation: item.deprecation(tcx), }); } } @@ -251,7 +252,17 @@ fn serialize(&self, serializer: S) -> Result )?; crate_data.serialize_field( "q", - &self.items.iter().map(|item| &item.path).collect::>(), + &self + .items + .iter() + .enumerate() + // Serialize as an array of item indices and full paths + .filter_map( + |(index, item)| { + if item.path.is_empty() { None } else { Some((index, &item.path)) } + }, + ) + .collect::>(), )?; crate_data.serialize_field( "d", @@ -304,6 +315,16 @@ fn serialize(&self, serializer: S) -> Result }) .collect::>(), )?; + crate_data.serialize_field( + "c", + &self + .items + .iter() + .enumerate() + // Serialize as an array of deprecated item indices + .filter_map(|(index, item)| item.deprecation.map(|_| index)) + .collect::>(), + )?; crate_data.serialize_field( "p", &self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::>(), diff --git a/src/librustdoc/html/render/sidebar.rs b/src/librustdoc/html/render/sidebar.rs new file mode 100644 index 00000000000..94ad4753d7c --- /dev/null +++ b/src/librustdoc/html/render/sidebar.rs @@ -0,0 +1,561 @@ +use std::{borrow::Cow, rc::Rc}; + +use askama::Template; +use rustc_data_structures::fx::FxHashSet; +use rustc_hir::{def::CtorKind, def_id::DefIdSet}; +use rustc_middle::ty::{self, TyCtxt}; + +use crate::{ + clean, + formats::{item_type::ItemType, Impl}, + html::{format::Buffer, markdown::IdMap}, +}; + +use super::{item_ty_to_section, Context, ItemSection}; + +#[derive(Template)] +#[template(path = "sidebar.html")] +pub(super) struct Sidebar<'a> { + pub(super) title_prefix: &'static str, + pub(super) title: &'a str, + pub(super) is_crate: bool, + pub(super) version: &'a str, + pub(super) blocks: Vec>, + pub(super) path: String, +} + +impl<'a> Sidebar<'a> { + /// Only create a `
    ` if there are any blocks + /// which should actually be rendered. + pub fn should_render_blocks(&self) -> bool { + self.blocks.iter().any(LinkBlock::should_render) + } +} + +/// A sidebar section such as 'Methods'. +pub(crate) struct LinkBlock<'a> { + /// The name of this section, e.g. 'Methods' + /// as well as the link to it, e.g. `#implementations`. + /// Will be rendered inside an `

    ` tag + heading: Link<'a>, + links: Vec>, + /// Render the heading even if there are no links + force_render: bool, +} + +impl<'a> LinkBlock<'a> { + pub fn new(heading: Link<'a>, links: Vec>) -> Self { + Self { heading, links, force_render: false } + } + + pub fn forced(heading: Link<'a>) -> Self { + Self { heading, links: vec![], force_render: true } + } + + pub fn should_render(&self) -> bool { + self.force_render || !self.links.is_empty() + } +} + +/// A link to an item. Content should not be escaped. +#[derive(PartialOrd, Ord, PartialEq, Eq, Hash, Clone)] +pub(crate) struct Link<'a> { + /// The content for the anchor tag + name: Cow<'a, str>, + /// The id of an anchor within the page (without a `#` prefix) + href: Cow<'a, str>, +} + +impl<'a> Link<'a> { + pub fn new(href: impl Into>, name: impl Into>) -> Self { + Self { href: href.into(), name: name.into() } + } + pub fn empty() -> Link<'static> { + Link::new("", "") + } +} + +pub(super) fn print_sidebar(cx: &Context<'_>, it: &clean::Item, buffer: &mut Buffer) { + let blocks: Vec> = match *it.kind { + clean::StructItem(ref s) => sidebar_struct(cx, it, s), + clean::TraitItem(ref t) => sidebar_trait(cx, it, t), + clean::PrimitiveItem(_) => sidebar_primitive(cx, it), + clean::UnionItem(ref u) => sidebar_union(cx, it, u), + clean::EnumItem(ref e) => sidebar_enum(cx, it, e), + clean::TypedefItem(_) => sidebar_typedef(cx, it), + clean::ModuleItem(ref m) => vec![sidebar_module(&m.items)], + clean::ForeignTypeItem => sidebar_foreign_type(cx, it), + _ => vec![], + }; + // The sidebar is designed to display sibling functions, modules and + // other miscellaneous information. since there are lots of sibling + // items (and that causes quadratic growth in large modules), + // we refactor common parts into a shared JavaScript file per module. + // still, we don't move everything into JS because we want to preserve + // as much HTML as possible in order to allow non-JS-enabled browsers + // to navigate the documentation (though slightly inefficiently). + let (title_prefix, title) = if it.is_struct() + || it.is_trait() + || it.is_primitive() + || it.is_union() + || it.is_enum() + || it.is_mod() + || it.is_typedef() + { + ( + match *it.kind { + clean::ModuleItem(..) if it.is_crate() => "Crate ", + clean::ModuleItem(..) => "Module ", + _ => "", + }, + it.name.as_ref().unwrap().as_str(), + ) + } else { + ("", "") + }; + let version = if it.is_crate() { + cx.cache().crate_version.as_ref().map(String::as_str).unwrap_or_default() + } else { + "" + }; + let path: String = if !it.is_mod() { + cx.current.iter().map(|s| s.as_str()).intersperse("::").collect() + } else { + "".into() + }; + let sidebar = Sidebar { title_prefix, title, is_crate: it.is_crate(), version, blocks, path }; + sidebar.render_into(buffer).unwrap(); +} + +fn get_struct_fields_name<'a>(fields: &'a [clean::Item]) -> Vec> { + let mut fields = fields + .iter() + .filter(|f| matches!(*f.kind, clean::StructFieldItem(..))) + .filter_map(|f| { + f.name.as_ref().map(|name| Link::new(format!("structfield.{name}"), name.as_str())) + }) + .collect::>>(); + fields.sort(); + fields +} + +fn sidebar_struct<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + s: &'a clean::Struct, +) -> Vec> { + let fields = get_struct_fields_name(&s.fields); + let field_name = match s.ctor_kind { + Some(CtorKind::Fn) => Some("Tuple Fields"), + None => Some("Fields"), + _ => None, + }; + let mut items = vec![]; + if let Some(name) = field_name { + items.push(LinkBlock::new(Link::new("fields", name), fields)); + } + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_trait<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + t: &'a clean::Trait, +) -> Vec> { + fn filter_items<'a>( + items: &'a [clean::Item], + filt: impl Fn(&clean::Item) -> bool, + ty: &str, + ) -> Vec> { + let mut res = items + .iter() + .filter_map(|m: &clean::Item| match m.name { + Some(ref name) if filt(m) => Some(Link::new(format!("{ty}.{name}"), name.as_str())), + _ => None, + }) + .collect::>>(); + res.sort(); + res + } + + let req_assoc = filter_items(&t.items, |m| m.is_ty_associated_type(), "associatedtype"); + let prov_assoc = filter_items(&t.items, |m| m.is_associated_type(), "associatedtype"); + let req_assoc_const = + filter_items(&t.items, |m| m.is_ty_associated_const(), "associatedconstant"); + let prov_assoc_const = + filter_items(&t.items, |m| m.is_associated_const(), "associatedconstant"); + let req_method = filter_items(&t.items, |m| m.is_ty_method(), "tymethod"); + let prov_method = filter_items(&t.items, |m| m.is_method(), "method"); + let mut foreign_impls = vec![]; + if let Some(implementors) = cx.cache().implementors.get(&it.item_id.expect_def_id()) { + foreign_impls.extend( + implementors + .iter() + .filter(|i| !i.is_on_local_type(cx)) + .filter_map(|i| super::extract_for_impl_name(&i.impl_item, cx)) + .map(|(name, id)| Link::new(id, name)), + ); + foreign_impls.sort(); + } + + let mut blocks: Vec> = [ + ("required-associated-types", "Required Associated Types", req_assoc), + ("provided-associated-types", "Provided Associated Types", prov_assoc), + ("required-associated-consts", "Required Associated Constants", req_assoc_const), + ("provided-associated-consts", "Provided Associated Constants", prov_assoc_const), + ("required-methods", "Required Methods", req_method), + ("provided-methods", "Provided Methods", prov_method), + ("foreign-impls", "Implementations on Foreign Types", foreign_impls), + ] + .into_iter() + .map(|(id, title, items)| LinkBlock::new(Link::new(id, title), items)) + .collect(); + sidebar_assoc_items(cx, it, &mut blocks); + blocks.push(LinkBlock::forced(Link::new("implementors", "Implementors"))); + if t.is_auto(cx.tcx()) { + blocks.push(LinkBlock::forced(Link::new("synthetic-implementors", "Auto Implementors"))); + } + blocks +} + +fn sidebar_primitive<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + if it.name.map(|n| n.as_str() != "reference").unwrap_or(false) { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items + } else { + let shared = Rc::clone(&cx.shared); + let (concrete, synthetic, blanket_impl) = + super::get_filtered_impls_for_reference(&shared, it); + + sidebar_render_assoc_items(cx, &mut IdMap::new(), concrete, synthetic, blanket_impl).into() + } +} + +fn sidebar_typedef<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +fn sidebar_union<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + u: &'a clean::Union, +) -> Vec> { + let fields = get_struct_fields_name(&u.fields); + let mut items = vec![LinkBlock::new(Link::new("fields", "Fields"), fields)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Adds trait implementations into the blocks of links +fn sidebar_assoc_items<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + links: &mut Vec>, +) { + let did = it.item_id.expect_def_id(); + let cache = cx.cache(); + + let mut assoc_consts = Vec::new(); + let mut methods = Vec::new(); + if let Some(v) = cache.impls.get(&did) { + let mut used_links = FxHashSet::default(); + let mut id_map = IdMap::new(); + + { + let used_links_bor = &mut used_links; + assoc_consts.extend( + v.iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_associated_constants(i.inner_impl(), used_links_bor)), + ); + // We want links' order to be reproducible so we don't use unstable sort. + assoc_consts.sort(); + + #[rustfmt::skip] // rustfmt makes the pipeline less readable + methods.extend( + v.iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_methods(i.inner_impl(), false, used_links_bor, false, cx.tcx())), + ); + + // We want links' order to be reproducible so we don't use unstable sort. + methods.sort(); + } + + let mut deref_methods = Vec::new(); + let [concrete, synthetic, blanket] = if v.iter().any(|i| i.inner_impl().trait_.is_some()) { + if let Some(impl_) = + v.iter().find(|i| i.trait_did() == cx.tcx().lang_items().deref_trait()) + { + let mut derefs = DefIdSet::default(); + derefs.insert(did); + sidebar_deref_methods( + cx, + &mut deref_methods, + impl_, + v, + &mut derefs, + &mut used_links, + ); + } + + let (synthetic, concrete): (Vec<&Impl>, Vec<&Impl>) = + v.iter().partition::, _>(|i| i.inner_impl().kind.is_auto()); + let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) = + concrete.into_iter().partition::, _>(|i| i.inner_impl().kind.is_blanket()); + + sidebar_render_assoc_items(cx, &mut id_map, concrete, synthetic, blanket_impl) + } else { + std::array::from_fn(|_| LinkBlock::new(Link::empty(), vec![])) + }; + + let mut blocks = vec![ + LinkBlock::new(Link::new("implementations", "Associated Constants"), assoc_consts), + LinkBlock::new(Link::new("implementations", "Methods"), methods), + ]; + blocks.append(&mut deref_methods); + blocks.extend([concrete, synthetic, blanket]); + links.append(&mut blocks); + } +} + +fn sidebar_deref_methods<'a>( + cx: &'a Context<'_>, + out: &mut Vec>, + impl_: &Impl, + v: &[Impl], + derefs: &mut DefIdSet, + used_links: &mut FxHashSet, +) { + let c = cx.cache(); + + debug!("found Deref: {:?}", impl_); + if let Some((target, real_target)) = + impl_.inner_impl().items.iter().find_map(|item| match *item.kind { + clean::AssocTypeItem(box ref t, _) => Some(match *t { + clean::Typedef { item_type: Some(ref type_), .. } => (type_, &t.type_), + _ => (&t.type_, &t.type_), + }), + _ => None, + }) + { + debug!("found target, real_target: {:?} {:?}", target, real_target); + if let Some(did) = target.def_id(c) && + let Some(type_did) = impl_.inner_impl().for_.def_id(c) && + // `impl Deref for S` + (did == type_did || !derefs.insert(did)) + { + // Avoid infinite cycles + return; + } + let deref_mut = v.iter().any(|i| i.trait_did() == cx.tcx().lang_items().deref_mut_trait()); + let inner_impl = target + .def_id(c) + .or_else(|| { + target.primitive_type().and_then(|prim| c.primitive_locations.get(&prim).cloned()) + }) + .and_then(|did| c.impls.get(&did)); + if let Some(impls) = inner_impl { + debug!("found inner_impl: {:?}", impls); + let mut ret = impls + .iter() + .filter(|i| i.inner_impl().trait_.is_none()) + .flat_map(|i| get_methods(i.inner_impl(), true, used_links, deref_mut, cx.tcx())) + .collect::>(); + if !ret.is_empty() { + let id = if let Some(target_def_id) = real_target.def_id(c) { + Cow::Borrowed( + cx.deref_id_map + .get(&target_def_id) + .expect("Deref section without derived id") + .as_str(), + ) + } else { + Cow::Borrowed("deref-methods") + }; + let title = format!( + "Methods from {:#}", + impl_.inner_impl().trait_.as_ref().unwrap().print(cx), + real_target.print(cx), + ); + // We want links' order to be reproducible so we don't use unstable sort. + ret.sort(); + out.push(LinkBlock::new(Link::new(id, title), ret)); + } + } + + // Recurse into any further impls that might exist for `target` + if let Some(target_did) = target.def_id(c) && + let Some(target_impls) = c.impls.get(&target_did) && + let Some(target_deref_impl) = target_impls.iter().find(|i| { + i.inner_impl() + .trait_ + .as_ref() + .map(|t| Some(t.def_id()) == cx.tcx().lang_items().deref_trait()) + .unwrap_or(false) + }) + { + sidebar_deref_methods( + cx, + out, + target_deref_impl, + target_impls, + derefs, + used_links, + ); + } + } +} + +fn sidebar_enum<'a>( + cx: &'a Context<'_>, + it: &'a clean::Item, + e: &'a clean::Enum, +) -> Vec> { + let mut variants = e + .variants() + .filter_map(|v| v.name) + .map(|name| Link::new(format!("variant.{name}"), name.to_string())) + .collect::>(); + variants.sort_unstable(); + + let mut items = vec![LinkBlock::new(Link::new("variants", "Variants"), variants)]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +pub(crate) fn sidebar_module_like( + item_sections_in_use: FxHashSet, +) -> LinkBlock<'static> { + let item_sections = ItemSection::ALL + .iter() + .copied() + .filter(|sec| item_sections_in_use.contains(sec)) + .map(|sec| Link::new(sec.id(), sec.name())) + .collect(); + LinkBlock::new(Link::empty(), item_sections) +} + +fn sidebar_module(items: &[clean::Item]) -> LinkBlock<'static> { + let item_sections_in_use: FxHashSet<_> = items + .iter() + .filter(|it| { + !it.is_stripped() + && it + .name + .or_else(|| { + if let clean::ImportItem(ref i) = *it.kind && + let clean::ImportKind::Simple(s) = i.kind { Some(s) } else { None } + }) + .is_some() + }) + .map(|it| item_ty_to_section(it.type_())) + .collect(); + + sidebar_module_like(item_sections_in_use) +} + +fn sidebar_foreign_type<'a>(cx: &'a Context<'_>, it: &'a clean::Item) -> Vec> { + let mut items = vec![]; + sidebar_assoc_items(cx, it, &mut items); + items +} + +/// Renders the trait implementations for this type +fn sidebar_render_assoc_items( + cx: &Context<'_>, + id_map: &mut IdMap, + concrete: Vec<&Impl>, + synthetic: Vec<&Impl>, + blanket_impl: Vec<&Impl>, +) -> [LinkBlock<'static>; 3] { + let format_impls = |impls: Vec<&Impl>, id_map: &mut IdMap| { + let mut links = FxHashSet::default(); + + let mut ret = impls + .iter() + .filter_map(|it| { + let trait_ = it.inner_impl().trait_.as_ref()?; + let encoded = + id_map.derive(super::get_id_for_impl(&it.inner_impl().for_, Some(trait_), cx)); + + let prefix = match it.inner_impl().polarity { + ty::ImplPolarity::Positive | ty::ImplPolarity::Reservation => "", + ty::ImplPolarity::Negative => "!", + }; + let generated = Link::new(encoded, format!("{prefix}{:#}", trait_.print(cx))); + if links.insert(generated.clone()) { Some(generated) } else { None } + }) + .collect::>>(); + ret.sort(); + ret + }; + + let concrete = format_impls(concrete, id_map); + let synthetic = format_impls(synthetic, id_map); + let blanket = format_impls(blanket_impl, id_map); + [ + LinkBlock::new(Link::new("trait-implementations", "Trait Implementations"), concrete), + LinkBlock::new( + Link::new("synthetic-implementations", "Auto Trait Implementations"), + synthetic, + ), + LinkBlock::new(Link::new("blanket-implementations", "Blanket Implementations"), blanket), + ] +} + +fn get_next_url(used_links: &mut FxHashSet, url: String) -> String { + if used_links.insert(url.clone()) { + return url; + } + let mut add = 1; + while !used_links.insert(format!("{}-{}", url, add)) { + add += 1; + } + format!("{}-{}", url, add) +} + +fn get_methods<'a>( + i: &'a clean::Impl, + for_deref: bool, + used_links: &mut FxHashSet, + deref_mut: bool, + tcx: TyCtxt<'_>, +) -> Vec> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_method() => { + if !for_deref || super::should_render_item(item, deref_mut, tcx) { + Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::Method, name)), + name.as_str(), + )) + } else { + None + } + } + _ => None, + }) + .collect::>() +} + +fn get_associated_constants<'a>( + i: &'a clean::Impl, + used_links: &mut FxHashSet, +) -> Vec> { + i.items + .iter() + .filter_map(|item| match item.name { + Some(ref name) if !name.is_empty() && item.is_associated_const() => Some(Link::new( + get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)), + name.as_str(), + )), + _ => None, + }) + .collect::>() +} diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index b98bced4126..3ba2ca75458 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -881,6 +881,13 @@ function initSearch(rawSearchIndex) { return a - b; } + // sort deprecated items later + a = aaa.item.deprecated; + b = bbb.item.deprecated; + if (a !== b) { + return a - b; + } + // sort by crate (current crate comes first) a = (aaa.item.crate !== preferredCrate); b = (bbb.item.crate !== preferredCrate); @@ -1244,6 +1251,7 @@ function initSearch(rawSearchIndex) { parent: item.parent, type: item.type, is_alias: true, + deprecated: item.deprecated, }; } @@ -2064,10 +2072,11 @@ function initSearch(rawSearchIndex) { * n: Array, * t: String, * d: Array, - * q: Array, + * q: Array<[Number, string]>, * i: Array, * f: Array, * p: Array, + * c: Array * }} */ const crateCorpus = rawSearchIndex[crate]; @@ -2086,6 +2095,7 @@ function initSearch(rawSearchIndex) { type: null, id: id, normalizedName: crate.indexOf("_") === -1 ? crate : crate.replace(/_/g, ""), + deprecated: null, }; id += 1; searchIndex.push(crateRow); @@ -2095,14 +2105,20 @@ function initSearch(rawSearchIndex) { const itemTypes = crateCorpus.t; // an array of (String) item names const itemNames = crateCorpus.n; - // an array of (String) full paths (or empty string for previous path) - const itemPaths = crateCorpus.q; + // an array of [(Number) item index, + // (String) full path] + // an item whose index is not present will fall back to the previous present path + // i.e. if indices 4 and 11 are present, but 5-10 and 12-13 are not present, + // 5-10 will fall back to the path for 4 and 12-13 will fall back to the path for 11 + const itemPaths = new Map(crateCorpus.q); // an array of (String) descriptions const itemDescs = crateCorpus.d; // an array of (Number) the parent path index + 1 to `paths`, or 0 if none const itemParentIdxs = crateCorpus.i; // an array of (Object | null) the type of the function, if any const itemFunctionSearchTypes = crateCorpus.f; + // an array of (Number) indices for the deprecated items + const deprecatedItems = new Set(crateCorpus.c); // an array of [(Number) item type, // (String) name] const paths = crateCorpus.p; @@ -2142,12 +2158,13 @@ function initSearch(rawSearchIndex) { crate: crate, ty: itemTypes.charCodeAt(i) - charA, name: itemNames[i], - path: itemPaths[i] ? itemPaths[i] : lastPath, + path: itemPaths.has(i) ? itemPaths.get(i) : lastPath, desc: itemDescs[i], parent: itemParentIdxs[i] > 0 ? paths[itemParentIdxs[i] - 1] : undefined, type: buildFunctionSearchType(itemFunctionSearchTypes[i], lowercasePaths), id: id, normalizedName: word.indexOf("_") === -1 ? word : word.replace(/_/g, ""), + deprecated: deprecatedItems.has(i), }; id += 1; searchIndex.push(row); diff --git a/src/librustdoc/html/templates/item_info.html b/src/librustdoc/html/templates/item_info.html new file mode 100644 index 00000000000..d2ea9bdae9c --- /dev/null +++ b/src/librustdoc/html/templates/item_info.html @@ -0,0 +1,7 @@ +{% if !items.is_empty() %} + {# #} + {% for item in items %} + {{item|safe}} {# #} + {% endfor %} + +{% endif %} diff --git a/src/librustdoc/html/templates/short_item_info.html b/src/librustdoc/html/templates/short_item_info.html new file mode 100644 index 00000000000..e3125af0e47 --- /dev/null +++ b/src/librustdoc/html/templates/short_item_info.html @@ -0,0 +1,23 @@ +{% match self %} + {% when Self::Deprecation with { message } %} +
    {# #} + 👎 {# #} + {{message}} {# #} +
    {# #} + {% when Self::Unstable with { feature, tracking } %} +
    {# #} + 🔬 {# #} + {# #} + This is a nightly-only experimental API. ({# #} + {{feature}} {# #} + {% match tracking %} + {% when Some with ((url, num)) %} +  #{{num}} {# #} + {% when None %} + {% endmatch %} + ) {# #} + {# #} +
    {# #} + {% when Self::Portability with { message } %} +
    {{message|safe}}
    {# #} +{% endmatch %} diff --git a/src/librustdoc/html/templates/sidebar.html b/src/librustdoc/html/templates/sidebar.html new file mode 100644 index 00000000000..01d476ad29f --- /dev/null +++ b/src/librustdoc/html/templates/sidebar.html @@ -0,0 +1,37 @@ +{% if !title.is_empty() %} +

    {# #} + {{title_prefix}}{{title}} {# #} +

    +{% endif %} + diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs index df26b36b7b3..4c4c003ca46 100644 --- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs +++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs @@ -369,10 +369,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty< Node::Item(item) => { if let ItemKind::Fn(_, _, body_id) = &item.kind && let output_ty = return_ty(cx, item.owner_id) - && Inherited::build(cx.tcx, item.owner_id.def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.owner_id.def_id); - fn_ctxt.can_coerce(ty, output_ty) - }) { + && let inherited = Inherited::new(cx.tcx, item.owner_id.def_id) + && let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.owner_id.def_id) + && fn_ctxt.can_coerce(ty, output_ty) + { if has_lifetime(output_ty) && has_lifetime(ty) { return false; } diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs index cddaf9450ea..62efd13b8d9 100644 --- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs +++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs @@ -33,38 +33,37 @@ pub(super) fn check_cast<'tcx>( let hir_id = e.hir_id; let local_def_id = hir_id.owner.def_id; - Inherited::build(cx.tcx, local_def_id).enter(|inherited| { - let fn_ctxt = FnCtxt::new(inherited, cx.param_env, local_def_id); + let inherited = Inherited::new(cx.tcx, local_def_id); + let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, local_def_id); - // If we already have errors, we can't be sure we can pointer cast. + // If we already have errors, we can't be sure we can pointer cast. + assert!( + !fn_ctxt.errors_reported_since_creation(), + "Newly created FnCtxt contained errors" + ); + + if let Ok(check) = cast::CastCheck::new( + &fn_ctxt, + e, + from_ty, + to_ty, + // We won't show any error to the user, so we don't care what the span is here. + DUMMY_SP, + DUMMY_SP, + hir::Constness::NotConst, + ) { + let res = check.do_check(&fn_ctxt); + + // do_check's documentation says that it might return Ok and create + // errors in the fcx instead of returning Err in some cases. Those cases + // should be filtered out before getting here. assert!( !fn_ctxt.errors_reported_since_creation(), - "Newly created FnCtxt contained errors" + "`fn_ctxt` contained errors after cast check!" ); - if let Ok(check) = cast::CastCheck::new( - &fn_ctxt, - e, - from_ty, - to_ty, - // We won't show any error to the user, so we don't care what the span is here. - DUMMY_SP, - DUMMY_SP, - hir::Constness::NotConst, - ) { - let res = check.do_check(&fn_ctxt); - - // do_check's documentation says that it might return Ok and create - // errors in the fcx instead of returning Err in some cases. Those cases - // should be filtered out before getting here. - assert!( - !fn_ctxt.errors_reported_since_creation(), - "`fn_ctxt` contained errors after cast check!" - ); - - res.ok() - } else { - None - } - }) + res.ok() + } else { + None + } } diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index 6d6d3c89a3c..868579b4b1a 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -62,8 +62,6 @@ "library/std/src/panic.rs", // fuchsia-specific panic backtrace handling "library/std/src/personality.rs", "library/std/src/personality/", - "library/std/src/thread/mod.rs", - "library/std/src/thread/local.rs", ]; pub fn check(path: &Path, bad: &mut bool) { diff --git a/tests/rustdoc-ui/crate-reference-in-block-module.rs b/tests/rustdoc-ui/crate-reference-in-block-module.rs new file mode 100644 index 00000000000..aede030e072 --- /dev/null +++ b/tests/rustdoc-ui/crate-reference-in-block-module.rs @@ -0,0 +1,5 @@ +// check-pass +fn main() { + /// [](crate) + struct X; +} diff --git a/tests/rustdoc-ui/crate-reference-in-block-module.stderr b/tests/rustdoc-ui/crate-reference-in-block-module.stderr new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/ui/check-cfg/my-awesome-platform.json b/tests/ui/check-cfg/my-awesome-platform.json new file mode 100644 index 00000000000..5e9ab8f1a2d --- /dev/null +++ b/tests/ui/check-cfg/my-awesome-platform.json @@ -0,0 +1,12 @@ +{ + "llvm-target": "x86_64-unknown-none-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "ericos", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "executables": true +} diff --git a/tests/ui/check-cfg/values-target-json.rs b/tests/ui/check-cfg/values-target-json.rs new file mode 100644 index 00000000000..2ef5a44592b --- /dev/null +++ b/tests/ui/check-cfg/values-target-json.rs @@ -0,0 +1,21 @@ +// This test checks that we don't lint values defined by a custom target (target json) +// +// check-pass +// needs-llvm-components: x86 +// compile-flags: --crate-type=lib --check-cfg=values() --target={{src-base}}/check-cfg/my-awesome-platform.json -Z unstable-options + +#![feature(lang_items, no_core, auto_traits)] +#![no_core] + +#[lang = "sized"] +trait Sized {} + +#[cfg(target_os = "linuz")] +//~^ WARNING unexpected `cfg` condition value +fn target_os_linux_misspell() {} + +#[cfg(target_os = "linux")] +fn target_os_linux() {} + +#[cfg(target_os = "ericos")] +fn target_os_ericos() {} diff --git a/tests/ui/check-cfg/values-target-json.stderr b/tests/ui/check-cfg/values-target-json.stderr new file mode 100644 index 00000000000..b58d2970773 --- /dev/null +++ b/tests/ui/check-cfg/values-target-json.stderr @@ -0,0 +1,13 @@ +warning: unexpected `cfg` condition value + --> $DIR/values-target-json.rs:13:7 + | +LL | #[cfg(target_os = "linuz")] + | ^^^^^^^^^^^^------- + | | + | help: did you mean: `"linux"` + | + = note: expected values for `target_os` are: aix, android, cuda, dragonfly, emscripten, ericos, espidf, freebsd, fuchsia, haiku, hermit, horizon, illumos, ios, l4re, linux, macos, netbsd, none, nto, openbsd, psp, redox, solaris, solid_asp3, tvos, uefi, unknown, vita, vxworks, wasi, watchos, windows, xous + = note: `#[warn(unexpected_cfgs)]` on by default + +warning: 1 warning emitted + diff --git a/tests/ui/macros/nonterminal-matching.stderr b/tests/ui/macros/nonterminal-matching.stderr index 5bbd5439098..762ecc3207f 100644 --- a/tests/ui/macros/nonterminal-matching.stderr +++ b/tests/ui/macros/nonterminal-matching.stderr @@ -18,6 +18,7 @@ LL | macro n(a $nt_item b) { ... LL | complex_nonterminal!(enum E {}); | ------------------------------- in this macro invocation + = note: captured metavariables except for `$tt`, `$ident` and `$lifetime` cannot be compared to other tokens = note: this error originates in the macro `complex_nonterminal` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui/threads-sendsync/issue-43733-2.rs b/tests/ui/threads-sendsync/issue-43733-2.rs index 32baeec4359..8f7a9c08375 100644 --- a/tests/ui/threads-sendsync/issue-43733-2.rs +++ b/tests/ui/threads-sendsync/issue-43733-2.rs @@ -21,7 +21,7 @@ const fn new() -> Self { } #[cfg(target_thread_local)] -use std::thread::__FastLocalKeyInner as Key; +use std::thread::__LocalKeyInner as Key; static __KEY: Key<()> = Key::new(); //~^ ERROR `UnsafeCell>` cannot be shared between threads diff --git a/tests/ui/threads-sendsync/issue-43733.rs b/tests/ui/threads-sendsync/issue-43733.rs index 935e02944b9..0eadef3e3e8 100644 --- a/tests/ui/threads-sendsync/issue-43733.rs +++ b/tests/ui/threads-sendsync/issue-43733.rs @@ -1,8 +1,8 @@ // ignore-wasm32 // revisions: mir thir // [thir]compile-flags: -Z thir-unsafeck -// normalize-stderr-test: "__FastLocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" -// normalize-stderr-test: "__OsLocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" +// normalize-stderr-test: "__LocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" +// normalize-stderr-test: "__LocalKeyInner::::get" -> "$$LOCALKEYINNER::::get" #![feature(thread_local)] #![feature(cfg_target_thread_local, thread_local_internals)] @@ -12,10 +12,10 @@ #[cfg(target_thread_local)] #[thread_local] -static __KEY: std::thread::__FastLocalKeyInner = std::thread::__FastLocalKeyInner::new(); +static __KEY: std::thread::__LocalKeyInner = std::thread::__LocalKeyInner::new(); #[cfg(not(target_thread_local))] -static __KEY: std::thread::__OsLocalKeyInner = std::thread::__OsLocalKeyInner::new(); +static __KEY: std::thread::__LocalKeyInner = std::thread::__LocalKeyInner::new(); fn __getit(_: Option<&mut Option>>) -> std::option::Option<&'static Foo> { __KEY.get(Default::default)