mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 00:03:43 +00:00
Auto merge of #109015 - matthiaskrgr:rollup-xu2s31g, r=matthiaskrgr
Rollup of 9 pull requests Successful merges: - #106276 (Fix `vec_deque::Drain` FIXME) - #107629 (rustdoc: sort deprecated items lower in search) - #108711 (Add note when matching token with nonterminal) - #108757 (rustdoc: Migrate `document_item_info` to Askama) - #108784 (rustdoc: Migrate sidebar rendering to Askama) - #108927 (Move __thread_local_inner to sys) - #108949 (Honor current target when checking conditional compilation values) - #108950 (Directly construct Inherited in typeck.) - #108988 (rustdoc: Don't crash on `crate` references in blocks) Failed merges: r? `@ghost` `@rustbot` modify labels: rollup
This commit is contained in:
commit
67e1681c1d
@ -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_parse::parser::{Parser, Recovery};
|
||||
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 {
|
||||
|
@ -4,7 +4,6 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
||||
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 @@ impl<'tcx> Deref for Inherited<'tcx> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<ty::TypeckResults<'tcx>>,
|
||||
}
|
||||
|
||||
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
|
||||
let 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<F, R>(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<ty::TypeckResults<'tcx>>) -> Self {
|
||||
let tcx = infcx.tcx;
|
||||
.with_opaque_type_inference(DefiningAnchor::Bind(hir_owner.def_id))
|
||||
.build();
|
||||
let typeck_results = RefCell::new(ty::TypeckResults::new(hir_owner));
|
||||
|
||||
Inherited {
|
||||
typeck_results,
|
||||
|
@ -45,13 +45,14 @@ mod rvalue_scopes;
|
||||
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,21 +197,22 @@ 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()
|
||||
} 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 {
|
||||
@ -230,52 +230,49 @@ fn typeck_with_fallback<'tcx>(
|
||||
|
||||
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)) {
|
||||
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 => fcx.next_ty_var(TypeVariableOrigin {
|
||||
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 => 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 }
|
||||
})),
|
||||
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())
|
||||
}
|
||||
hir::InlineAsmOperand::SymFn { anon_const }
|
||||
if anon_const.hir_id == id =>
|
||||
{
|
||||
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)
|
||||
})
|
||||
}
|
||||
_ => fallback(),
|
||||
},
|
||||
_ => fallback(),
|
||||
});
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let expected_type = expected_type.unwrap_or_else(fallback);
|
||||
|
||||
let expected_type = fcx.normalize(body.value.span, expected_type);
|
||||
fcx.require_type_is_sized(expected_type, body.value.span, traits::ConstSized);
|
||||
@ -333,8 +330,7 @@ fn typeck_with_fallback<'tcx>(
|
||||
|
||||
fcx.infcx.skip_region_resolution();
|
||||
|
||||
fcx.resolve_type_vars_in_body(body)
|
||||
});
|
||||
let typeck_results = fcx.resolve_type_vars_in_body(body);
|
||||
|
||||
// Consistency check our TypeckResults instance can hold all ItemLocalIds
|
||||
// it will need to hold.
|
||||
|
@ -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;
|
||||
|
@ -1849,20 +1849,12 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
|
||||
&mut self,
|
||||
path_str: &str,
|
||||
ns: Namespace,
|
||||
mut parent_scope: ParentScope<'a>,
|
||||
parent_scope: ParentScope<'a>,
|
||||
) -> Option<Res> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1137,7 +1137,7 @@ impl CrateCheckConfig {
|
||||
}
|
||||
|
||||
/// 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 @@ impl CrateCheckConfig {
|
||||
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 @@ impl CrateCheckConfig {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,36 +52,22 @@ impl<'a, T, A: Allocator> Drain<'a, T, A> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
@ -1156,7 +1156,7 @@ impl<T, A: Allocator> VecDeque<T, A> {
|
||||
#[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 @@ impl<T, A: Allocator> VecDeque<T, A> {
|
||||
#[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 @@ impl<T, A: Allocator> VecDeque<T, A> {
|
||||
|
||||
/// 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<R>(&self, range: R) -> (Range<usize>, Range<usize>)
|
||||
/// 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<R>(&self, range: R, len: usize) -> (Range<usize>, Range<usize>)
|
||||
where
|
||||
R: RangeBounds<usize>,
|
||||
{
|
||||
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 @@ impl<T, A: Allocator> VecDeque<T, A> {
|
||||
where
|
||||
R: RangeBounds<usize>,
|
||||
{
|
||||
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 @@ impl<T, A: Allocator> VecDeque<T, A> {
|
||||
where
|
||||
R: RangeBounds<usize>,
|
||||
{
|
||||
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
|
||||
|
@ -12,6 +12,7 @@
|
||||
|
||||
pub mod alloc;
|
||||
pub mod small_c_string;
|
||||
pub mod thread_local;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
276
library/std/src/sys/common/thread_local/fast_local.rs
Normal file
276
library/std/src/sys/common/thread_local/fast_local.rs
Normal file
@ -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<T> {
|
||||
// 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<T>,
|
||||
|
||||
// Metadata to keep track of the state of the destructor. Remember that
|
||||
// this variable is thread-local, not global.
|
||||
dtor_state: Cell<DtorState>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Key<T> {
|
||||
pub const fn new() -> Key<T> {
|
||||
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<F: FnOnce() -> 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<F: FnOnce() -> T>(&self, init: F) -> Option<&'static T> {
|
||||
// SAFETY: See comment above (this function doc).
|
||||
if !mem::needs_drop::<T>() || 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<T>`
|
||||
// is safe because the function will build a pointer to a
|
||||
// Key<T>, which is the type of self and so find the correct
|
||||
// size.
|
||||
unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::<T>) };
|
||||
self.dtor_state.set(DtorState::Registered);
|
||||
true
|
||||
}
|
||||
DtorState::Registered => {
|
||||
// recursively initialized
|
||||
true
|
||||
}
|
||||
DtorState::RunningOrHasRun => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn destroy_value<T>(ptr: *mut u8) {
|
||||
let ptr = ptr as *mut Key<T>;
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
// The pointer `ptr` has been built just above and comes from
|
||||
// `try_register_dtor` where it is originally a Key<T> 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<T>` 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");
|
||||
}
|
||||
}
|
||||
}
|
109
library/std/src/sys/common/thread_local/mod.rs
Normal file
109
library/std/src/sys/common/thread_local/mod.rs
Normal file
@ -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<T> {
|
||||
inner: UnsafeCell<Option<T>>,
|
||||
}
|
||||
|
||||
impl<T> LazyKeyInner<T> {
|
||||
pub const fn new() -> LazyKeyInner<T> {
|
||||
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<T> 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<F: FnOnce() -> 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<T> {
|
||||
// SAFETY: See doc comment for this method.
|
||||
unsafe { (*self.inner.get()).take() }
|
||||
}
|
||||
}
|
||||
}
|
217
library/std/src/sys/common/thread_local/os_local.rs
Normal file
217
library/std/src/sys/common/thread_local/os_local.rs
Normal file
@ -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<T> {
|
||||
// OS-TLS key that we'll use to key off.
|
||||
os: OsStaticKey,
|
||||
marker: marker::PhantomData<Cell<T>>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for Key<T> {}
|
||||
|
||||
struct Value<T: 'static> {
|
||||
inner: LazyKeyInner<T>,
|
||||
key: &'static Key<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Key<T> {
|
||||
#[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
|
||||
pub const fn new() -> Key<T> {
|
||||
Key { os: OsStaticKey::new(Some(destroy_value::<T>)), 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<T> };
|
||||
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<T> };
|
||||
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<T: 'static>(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<T>);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
115
library/std/src/sys/common/thread_local/static_local.rs
Normal file
115
library/std/src/sys/common/thread_local/static_local.rs
Normal file
@ -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<T> {
|
||||
inner: LazyKeyInner<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for Key<T> {}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Key<T> {
|
||||
pub const fn new() -> Key<T> {
|
||||
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<T> 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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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 @@ impl<T: 'static> LocalKey<RefCell<T>> {
|
||||
self.with(|cell| cell.replace(value))
|
||||
}
|
||||
}
|
||||
|
||||
mod lazy {
|
||||
use crate::cell::UnsafeCell;
|
||||
use crate::hint;
|
||||
use crate::mem;
|
||||
|
||||
pub struct LazyKeyInner<T> {
|
||||
inner: UnsafeCell<Option<T>>,
|
||||
}
|
||||
|
||||
impl<T> LazyKeyInner<T> {
|
||||
pub const fn new() -> LazyKeyInner<T> {
|
||||
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<T> 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<F: FnOnce() -> 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<T> {
|
||||
// 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<T> {
|
||||
inner: LazyKeyInner<T>,
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for Key<T> {}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Key<T> {
|
||||
pub const fn new() -> Key<T> {
|
||||
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<T> 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<T> {
|
||||
// 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<T>,
|
||||
|
||||
// Metadata to keep track of the state of the destructor. Remember that
|
||||
// this variable is thread-local, not global.
|
||||
dtor_state: Cell<DtorState>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Key<T> {
|
||||
pub const fn new() -> Key<T> {
|
||||
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<F: FnOnce() -> 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<F: FnOnce() -> T>(&self, init: F) -> Option<&'static T> {
|
||||
// SAFETY: See comment above (this function doc).
|
||||
if !mem::needs_drop::<T>() || 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<T>`
|
||||
// is safe because the function will build a pointer to a
|
||||
// Key<T>, which is the type of self and so find the correct
|
||||
// size.
|
||||
unsafe { register_dtor(self as *const _ as *mut u8, destroy_value::<T>) };
|
||||
self.dtor_state.set(DtorState::Registered);
|
||||
true
|
||||
}
|
||||
DtorState::Registered => {
|
||||
// recursively initialized
|
||||
true
|
||||
}
|
||||
DtorState::RunningOrHasRun => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn destroy_value<T>(ptr: *mut u8) {
|
||||
let ptr = ptr as *mut Key<T>;
|
||||
|
||||
// SAFETY:
|
||||
//
|
||||
// The pointer `ptr` has been built just above and comes from
|
||||
// `try_register_dtor` where it is originally a Key<T> 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<T>` 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<T> {
|
||||
// OS-TLS key that we'll use to key off.
|
||||
os: OsStaticKey,
|
||||
marker: marker::PhantomData<Cell<T>>,
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for Key<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Key").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<T> Sync for Key<T> {}
|
||||
|
||||
struct Value<T: 'static> {
|
||||
inner: LazyKeyInner<T>,
|
||||
key: &'static Key<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Key<T> {
|
||||
#[rustc_const_unstable(feature = "thread_local_internals", issue = "none")]
|
||||
pub const fn new() -> Key<T> {
|
||||
Key { os: OsStaticKey::new(Some(destroy_value::<T>)), 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<T> };
|
||||
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<T> };
|
||||
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<T: 'static>(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<T>);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -203,44 +203,9 @@ pub use self::local::{AccessError, LocalKey};
|
||||
// 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
|
||||
|
@ -346,6 +346,7 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> {
|
||||
self.cache,
|
||||
),
|
||||
aliases: item.attrs.get_doc_aliases(),
|
||||
deprecation: item.deprecation(self.tcx),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,11 @@ use super::print_item::{full_path, item_path, print_item};
|
||||
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::url_parts_builder::UrlPartsBuilder;
|
||||
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 @@ impl<'tcx> FormatRenderer<'tcx> for Context<'tcx> {
|
||||
};
|
||||
let all = shared.all.replace(AllTypes::new());
|
||||
let mut sidebar = Buffer::html();
|
||||
write!(sidebar, "<h2 class=\"location\"><a href=\"#\">Crate {}</a></h2>", crate_name);
|
||||
|
||||
let mut items = Buffer::html();
|
||||
sidebar_module_like(&mut items, all.item_sections());
|
||||
if !items.is_empty() {
|
||||
sidebar.push_str("<div class=\"sidebar-elems\">");
|
||||
sidebar.push_buffer(items);
|
||||
sidebar.push_str("</div>");
|
||||
}
|
||||
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,
|
||||
|
@ -30,6 +30,7 @@ mod tests;
|
||||
|
||||
mod context;
|
||||
mod print_item;
|
||||
mod sidebar;
|
||||
mod span_map;
|
||||
mod write_shared;
|
||||
|
||||
@ -46,14 +47,13 @@ use std::rc::Rc;
|
||||
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<usize>,
|
||||
pub(crate) search_type: Option<IndexItemFunctionType>,
|
||||
pub(crate) aliases: Box<[Symbol]>,
|
||||
pub(crate) deprecation: Option<Deprecation>,
|
||||
}
|
||||
|
||||
/// 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<ShortItemInfo>,
|
||||
}
|
||||
/// 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("<span class=\"item-info\">");
|
||||
for info in item_infos {
|
||||
w.write_str(&info);
|
||||
}
|
||||
w.write_str("</span>");
|
||||
}
|
||||
) -> ItemInfo {
|
||||
let items = short_item_info(item, cx, parent);
|
||||
ItemInfo { items }
|
||||
}
|
||||
|
||||
fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<String> {
|
||||
@ -567,7 +566,25 @@ fn portability(item: &clean::Item, parent: Option<&clean::Item>) -> Option<Strin
|
||||
cfg
|
||||
);
|
||||
|
||||
Some(format!("<div class=\"stab portability\">{}</div>", 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<String> {
|
||||
) -> Vec<ShortItemInfo> {
|
||||
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!(
|
||||
"<div class=\"stab deprecated\">\
|
||||
<span class=\"emoji\">👎</span>\
|
||||
<span>{}</span>\
|
||||
</div>",
|
||||
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 = "<span class=\"emoji\">🔬</span>\
|
||||
<span>This is a nightly-only experimental API."
|
||||
.to_owned();
|
||||
|
||||
let mut feature = format!("<code>{}</code>", Escape(feature.as_str()));
|
||||
if let (Some(url), Some(issue)) = (&cx.shared.issue_tracker_base_url, issue) {
|
||||
feature.push_str(&format!(
|
||||
" <a href=\"{url}{issue}\">#{issue}</a>",
|
||||
url = url,
|
||||
issue = issue
|
||||
));
|
||||
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 });
|
||||
}
|
||||
|
||||
message.push_str(&format!(" ({})</span>", feature));
|
||||
|
||||
extra_info.push(format!("<div class=\"stab unstable\">{}</div>", message));
|
||||
}
|
||||
|
||||
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 render_impl(
|
||||
// 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 render_impl(
|
||||
}
|
||||
}
|
||||
} 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, "<span class=\"item-info\">{}</span>", portability);
|
||||
write!(
|
||||
w,
|
||||
"<span class=\"item-info\"><div class=\"stab portability\">{}</div></span>",
|
||||
portability
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
w.write_str("</section>");
|
||||
}
|
||||
|
||||
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,
|
||||
"<h2 class=\"location\"><a href=\"#\">{}{}</a></h2>",
|
||||
match *it.kind {
|
||||
clean::ModuleItem(..) =>
|
||||
if it.is_crate() {
|
||||
"Crate "
|
||||
} else {
|
||||
"Module "
|
||||
},
|
||||
_ => "",
|
||||
},
|
||||
it.name.as_ref().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
buffer.write_str("<div class=\"sidebar-elems\">");
|
||||
if it.is_crate() {
|
||||
write!(buffer, "<ul class=\"block\">");
|
||||
if let Some(ref version) = cx.cache().crate_version {
|
||||
write!(buffer, "<li class=\"version\">Version {}</li>", Escape(version));
|
||||
}
|
||||
write!(buffer, "<li><a id=\"all-types\" href=\"all.html\">All Items</a></li>");
|
||||
buffer.write_str("</ul>");
|
||||
}
|
||||
|
||||
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, "<h2><a href=\"index.html\">In {}</a></h2>", path);
|
||||
}
|
||||
|
||||
// Closes sidebar-elems div.
|
||||
buffer.write_str("</div>");
|
||||
}
|
||||
|
||||
fn get_next_url(used_links: &mut FxHashSet<String>, 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, "<a href=\"#{}\">{}</a>", self.url, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for SidebarLink {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.url == other.url
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for SidebarLink {}
|
||||
|
||||
impl PartialOrd for SidebarLink {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for SidebarLink {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.url.cmp(&other.url)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_methods(
|
||||
i: &clean::Impl,
|
||||
for_deref: bool,
|
||||
used_links: &mut FxHashSet<String>,
|
||||
deref_mut: bool,
|
||||
tcx: TyCtxt<'_>,
|
||||
) -> Vec<SidebarLink> {
|
||||
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::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_associated_constants(
|
||||
i: &clean::Impl,
|
||||
used_links: &mut FxHashSet<String>,
|
||||
) -> Vec<SidebarLink> {
|
||||
i.items
|
||||
.iter()
|
||||
.filter_map(|item| match item.name {
|
||||
Some(name) if !name.is_empty() && item.is_associated_const() => Some(SidebarLink {
|
||||
name,
|
||||
url: get_next_url(used_links, format!("{}.{}", ItemType::AssocConst, name)),
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
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 @@ pub(crate) fn small_url_encode(s: String) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
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!("<a href=\"#{}\">{}{}</a>", encoded, prefix, out);
|
||||
if links.insert(generated.clone()) { Some(generated) } else { None }
|
||||
})
|
||||
.collect::<Vec<String>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>>();
|
||||
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::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
|
||||
let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
||||
concrete.into_iter().partition::<Vec<_>, _>(|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<String>,
|
||||
) {
|
||||
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<Target = S> 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::<Vec<_>>();
|
||||
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, "<section>{}</section>", 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, "<h3><a href=\"#{}\">{}</a></h3>", id, title);
|
||||
}
|
||||
|
||||
fn print_sidebar_block(
|
||||
buf: &mut Buffer,
|
||||
id: &str,
|
||||
title: &str,
|
||||
items: impl Iterator<Item = impl fmt::Display>,
|
||||
) {
|
||||
print_sidebar_title(buf, id, title);
|
||||
buf.push_str("<ul class=\"block\">");
|
||||
for item in items {
|
||||
write!(buf, "<li>{}</li>", item);
|
||||
}
|
||||
buf.push_str("</ul>");
|
||||
}
|
||||
|
||||
fn sidebar_trait(cx: &Context<'_>, buf: &mut Buffer, it: &clean::Item, t: &clean::Trait) {
|
||||
buf.write_str("<section>");
|
||||
|
||||
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::<Vec<_>>();
|
||||
|
||||
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!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
|
||||
);
|
||||
|
||||
print_sidebar_section(
|
||||
buf,
|
||||
&t.items,
|
||||
"provided-associated-types",
|
||||
"Provided Associated Types",
|
||||
|m| m.is_associated_type(),
|
||||
|sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocType),
|
||||
);
|
||||
|
||||
print_sidebar_section(
|
||||
buf,
|
||||
&t.items,
|
||||
"required-associated-consts",
|
||||
"Required Associated Constants",
|
||||
|m| m.is_ty_associated_const(),
|
||||
|sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
|
||||
);
|
||||
|
||||
print_sidebar_section(
|
||||
buf,
|
||||
&t.items,
|
||||
"provided-associated-consts",
|
||||
"Provided Associated Constants",
|
||||
|m| m.is_associated_const(),
|
||||
|sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::AssocConst),
|
||||
);
|
||||
|
||||
print_sidebar_section(
|
||||
buf,
|
||||
&t.items,
|
||||
"required-methods",
|
||||
"Required Methods",
|
||||
|m| m.is_ty_method(),
|
||||
|sym| format!("<a href=\"#{1}.{0}\">{0}</a>", sym, ItemType::TyMethod),
|
||||
);
|
||||
|
||||
print_sidebar_section(
|
||||
buf,
|
||||
&t.items,
|
||||
"provided-methods",
|
||||
"Provided Methods",
|
||||
|m| m.is_method(),
|
||||
|sym| format!("<a href=\"#{1}.{0}\">{0}</a>", 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::<Vec<_>>();
|
||||
|
||||
if !res.is_empty() {
|
||||
res.sort();
|
||||
print_sidebar_block(
|
||||
buf,
|
||||
"foreign-impls",
|
||||
"Implementations on Foreign Types",
|
||||
res.iter().map(|(name, id)| format!("<a href=\"#{}\">{}</a>", 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("</section>")
|
||||
}
|
||||
|
||||
/// 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<T> 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, "<section>{}</section>", 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, "<section>{}</section>", sidebar.into_inner());
|
||||
}
|
||||
}
|
||||
|
||||
fn get_struct_fields_name(fields: &[clean::Item]) -> Vec<String> {
|
||||
let mut fields = fields
|
||||
.iter()
|
||||
.filter(|f| matches!(*f.kind, clean::StructFieldItem(..)))
|
||||
.filter_map(|f| {
|
||||
f.name.map(|name| format!("<a href=\"#structfield.{name}\">{name}</a>", name = name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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, "<section>{}</section>", 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!("<a href=\"#variant.{name}\">{name}</a>", name = name))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
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, "<section>{}</section>", 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<ItemSection>) {
|
||||
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, "<li><a href=\"#{}\">{}</a></li>", sec.id(), sec.name());
|
||||
}
|
||||
|
||||
if !sidebar.is_empty() {
|
||||
write!(
|
||||
buf,
|
||||
"<section>\
|
||||
<ul class=\"block\">{}</ul>\
|
||||
</section>",
|
||||
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, "<section>{}</section>", 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
|
||||
|
@ -470,10 +470,11 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) ->
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
@ -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 @@ pub(crate) fn build_index<'tcx>(
|
||||
)?;
|
||||
crate_data.serialize_field(
|
||||
"q",
|
||||
&self.items.iter().map(|item| &item.path).collect::<Vec<_>>(),
|
||||
&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::<Vec<_>>(),
|
||||
)?;
|
||||
crate_data.serialize_field(
|
||||
"d",
|
||||
@ -304,6 +315,16 @@ pub(crate) fn build_index<'tcx>(
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)?;
|
||||
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::<Vec<_>>(),
|
||||
)?;
|
||||
crate_data.serialize_field(
|
||||
"p",
|
||||
&self.paths.iter().map(|(it, s)| (it, s.as_str())).collect::<Vec<_>>(),
|
||||
|
561
src/librustdoc/html/render/sidebar.rs
Normal file
561
src/librustdoc/html/render/sidebar.rs
Normal file
@ -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<LinkBlock<'a>>,
|
||||
pub(super) path: String,
|
||||
}
|
||||
|
||||
impl<'a> Sidebar<'a> {
|
||||
/// Only create a `<section>` 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 `<h3>` tag
|
||||
heading: Link<'a>,
|
||||
links: Vec<Link<'a>>,
|
||||
/// Render the heading even if there are no links
|
||||
force_render: bool,
|
||||
}
|
||||
|
||||
impl<'a> LinkBlock<'a> {
|
||||
pub fn new(heading: Link<'a>, links: Vec<Link<'a>>) -> 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<Cow<'a, str>>, name: impl Into<Cow<'a, str>>) -> 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<LinkBlock<'_>> = 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<Link<'a>> {
|
||||
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::<Vec<Link<'a>>>();
|
||||
fields.sort();
|
||||
fields
|
||||
}
|
||||
|
||||
fn sidebar_struct<'a>(
|
||||
cx: &'a Context<'_>,
|
||||
it: &'a clean::Item,
|
||||
s: &'a clean::Struct,
|
||||
) -> Vec<LinkBlock<'a>> {
|
||||
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<LinkBlock<'a>> {
|
||||
fn filter_items<'a>(
|
||||
items: &'a [clean::Item],
|
||||
filt: impl Fn(&clean::Item) -> bool,
|
||||
ty: &str,
|
||||
) -> Vec<Link<'a>> {
|
||||
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::<Vec<Link<'a>>>();
|
||||
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<LinkBlock<'_>> = [
|
||||
("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<LinkBlock<'a>> {
|
||||
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<LinkBlock<'a>> {
|
||||
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<LinkBlock<'a>> {
|
||||
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<LinkBlock<'a>>,
|
||||
) {
|
||||
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::<Vec<_>, _>(|i| i.inner_impl().kind.is_auto());
|
||||
let (blanket_impl, concrete): (Vec<&Impl>, Vec<&Impl>) =
|
||||
concrete.into_iter().partition::<Vec<_>, _>(|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<LinkBlock<'a>>,
|
||||
impl_: &Impl,
|
||||
v: &[Impl],
|
||||
derefs: &mut DefIdSet,
|
||||
used_links: &mut FxHashSet<String>,
|
||||
) {
|
||||
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<Target = S> 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::<Vec<_>>();
|
||||
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 {:#}<Target={:#}>",
|
||||
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<LinkBlock<'a>> {
|
||||
let mut variants = e
|
||||
.variants()
|
||||
.filter_map(|v| v.name)
|
||||
.map(|name| Link::new(format!("variant.{name}"), name.to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
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<ItemSection>,
|
||||
) -> 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<LinkBlock<'a>> {
|
||||
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::<Vec<Link<'static>>>();
|
||||
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<String>, 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<String>,
|
||||
deref_mut: bool,
|
||||
tcx: TyCtxt<'_>,
|
||||
) -> Vec<Link<'a>> {
|
||||
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::<Vec<_>>()
|
||||
}
|
||||
|
||||
fn get_associated_constants<'a>(
|
||||
i: &'a clean::Impl,
|
||||
used_links: &mut FxHashSet<String>,
|
||||
) -> Vec<Link<'a>> {
|
||||
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::<Vec<_>>()
|
||||
}
|
@ -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<string>,
|
||||
* t: String,
|
||||
* d: Array<string>,
|
||||
* q: Array<string>,
|
||||
* q: Array<[Number, string]>,
|
||||
* i: Array<Number>,
|
||||
* f: Array<RawFunctionSearchType>,
|
||||
* p: Array<Object>,
|
||||
* c: Array<Number>
|
||||
* }}
|
||||
*/
|
||||
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);
|
||||
|
7
src/librustdoc/html/templates/item_info.html
Normal file
7
src/librustdoc/html/templates/item_info.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% if !items.is_empty() %}
|
||||
<span class="item-info"> {# #}
|
||||
{% for item in items %}
|
||||
{{item|safe}} {# #}
|
||||
{% endfor %}
|
||||
</span>
|
||||
{% endif %}
|
23
src/librustdoc/html/templates/short_item_info.html
Normal file
23
src/librustdoc/html/templates/short_item_info.html
Normal file
@ -0,0 +1,23 @@
|
||||
{% match self %}
|
||||
{% when Self::Deprecation with { message } %}
|
||||
<div class="stab deprecated"> {# #}
|
||||
<span class="emoji">👎</span> {# #}
|
||||
<span>{{message}}</span> {# #}
|
||||
</div> {# #}
|
||||
{% when Self::Unstable with { feature, tracking } %}
|
||||
<div class="stab unstable"> {# #}
|
||||
<span class="emoji">🔬</span> {# #}
|
||||
<span> {# #}
|
||||
This is a nightly-only experimental API. ({# #}
|
||||
<code>{{feature}}</code> {# #}
|
||||
{% match tracking %}
|
||||
{% when Some with ((url, num)) %}
|
||||
<a href="{{url}}{{num}}">#{{num}}</a> {# #}
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
) {# #}
|
||||
</span> {# #}
|
||||
</div> {# #}
|
||||
{% when Self::Portability with { message } %}
|
||||
<div class="stab portability">{{message|safe}}</div> {# #}
|
||||
{% endmatch %}
|
37
src/librustdoc/html/templates/sidebar.html
Normal file
37
src/librustdoc/html/templates/sidebar.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% if !title.is_empty() %}
|
||||
<h2 class="location"> {# #}
|
||||
<a href="#">{{title_prefix}}{{title}}</a> {# #}
|
||||
</h2>
|
||||
{% endif %}
|
||||
<div class="sidebar-elems">
|
||||
{% if is_crate %}
|
||||
<ul class="block">
|
||||
{% if !version.is_empty() %}
|
||||
<li class="version">Version {{+ version}}</li>
|
||||
{% endif %}
|
||||
<li><a id="all-types" href="all.html">All Items</a></li> {# #}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if self.should_render_blocks() %}
|
||||
<section>
|
||||
{% for block in blocks %}
|
||||
{% if block.should_render() %}
|
||||
{% if !block.heading.name.is_empty() %}
|
||||
<h3><a href="#{{block.heading.href|safe}}">{{block.heading.name}}</a></h3>
|
||||
{% endif %}
|
||||
{% if !block.links.is_empty() %}
|
||||
<ul class="block">
|
||||
{% for link in block.links %}
|
||||
<li><a href="#{{link.href|safe}}">{{link.name}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</section>
|
||||
{% endif %}
|
||||
{% if !path.is_empty() %}
|
||||
<h2><a href="index.html">In {{+ path}}</a></h2>
|
||||
{% endif %}
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ 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.
|
||||
assert!(
|
||||
@ -66,5 +66,4 @@ pub(super) fn check_cast<'tcx>(
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -62,8 +62,6 @@ const EXCEPTION_PATHS: &[&str] = &[
|
||||
"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) {
|
||||
|
5
tests/rustdoc-ui/crate-reference-in-block-module.rs
Normal file
5
tests/rustdoc-ui/crate-reference-in-block-module.rs
Normal file
@ -0,0 +1,5 @@
|
||||
// check-pass
|
||||
fn main() {
|
||||
/// [](crate)
|
||||
struct X;
|
||||
}
|
12
tests/ui/check-cfg/my-awesome-platform.json
Normal file
12
tests/ui/check-cfg/my-awesome-platform.json
Normal file
@ -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
|
||||
}
|
21
tests/ui/check-cfg/values-target-json.rs
Normal file
21
tests/ui/check-cfg/values-target-json.rs
Normal file
@ -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() {}
|
13
tests/ui/check-cfg/values-target-json.stderr
Normal file
13
tests/ui/check-cfg/values-target-json.stderr
Normal file
@ -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
|
||||
|
@ -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
|
||||
|
@ -21,7 +21,7 @@ impl<T> Key<T> {
|
||||
}
|
||||
|
||||
#[cfg(target_thread_local)]
|
||||
use std::thread::__FastLocalKeyInner as Key;
|
||||
use std::thread::__LocalKeyInner as Key;
|
||||
|
||||
static __KEY: Key<()> = Key::new();
|
||||
//~^ ERROR `UnsafeCell<Option<()>>` cannot be shared between threads
|
||||
|
@ -1,8 +1,8 @@
|
||||
// ignore-wasm32
|
||||
// revisions: mir thir
|
||||
// [thir]compile-flags: -Z thir-unsafeck
|
||||
// normalize-stderr-test: "__FastLocalKeyInner::<T>::get" -> "$$LOCALKEYINNER::<T>::get"
|
||||
// normalize-stderr-test: "__OsLocalKeyInner::<T>::get" -> "$$LOCALKEYINNER::<T>::get"
|
||||
// normalize-stderr-test: "__LocalKeyInner::<T>::get" -> "$$LOCALKEYINNER::<T>::get"
|
||||
// normalize-stderr-test: "__LocalKeyInner::<T>::get" -> "$$LOCALKEYINNER::<T>::get"
|
||||
#![feature(thread_local)]
|
||||
#![feature(cfg_target_thread_local, thread_local_internals)]
|
||||
|
||||
@ -12,10 +12,10 @@ type Foo = std::cell::RefCell<String>;
|
||||
|
||||
#[cfg(target_thread_local)]
|
||||
#[thread_local]
|
||||
static __KEY: std::thread::__FastLocalKeyInner<Foo> = std::thread::__FastLocalKeyInner::new();
|
||||
static __KEY: std::thread::__LocalKeyInner<Foo> = std::thread::__LocalKeyInner::new();
|
||||
|
||||
#[cfg(not(target_thread_local))]
|
||||
static __KEY: std::thread::__OsLocalKeyInner<Foo> = std::thread::__OsLocalKeyInner::new();
|
||||
static __KEY: std::thread::__LocalKeyInner<Foo> = std::thread::__LocalKeyInner::new();
|
||||
|
||||
fn __getit(_: Option<&mut Option<RefCell<String>>>) -> std::option::Option<&'static Foo> {
|
||||
__KEY.get(Default::default)
|
||||
|
Loading…
Reference in New Issue
Block a user