Auto merge of #121904 - matthiaskrgr:rollup-qcq0d3h, r=matthiaskrgr

Rollup of 7 pull requests

Successful merges:

 - #121666 (Use the OS thread name by default if `THREAD_INFO` has not been initialized)
 - #121758 (Move thread local implementation to `sys`)
 - #121759 (attempt to further clarify addr_of docs)
 - #121855 (Correctly generate item info of trait items)
 - #121888 (style library/core/src/error.rs)
 - #121892 (The ordinary lowering of `thir::ExprKind::Let` is unreachable)
 - #121895 (avoid collecting into vecs in some places)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2024-03-02 20:34:58 +00:00
commit 5119208fd7
33 changed files with 269 additions and 107 deletions

View file

@ -357,31 +357,27 @@ fn add_unused_functions(cx: &CodegenCx<'_, '_>) {
let ignore_unused_generics = tcx.sess.instrument_coverage_except_unused_generics();
let eligible_def_ids: Vec<DefId> = tcx
.mir_keys(())
.iter()
.filter_map(|local_def_id| {
let def_id = local_def_id.to_def_id();
let kind = tcx.def_kind(def_id);
// `mir_keys` will give us `DefId`s for all kinds of things, not
// just "functions", like consts, statics, etc. Filter those out.
// If `ignore_unused_generics` was specified, filter out any
// generic functions from consideration as well.
if !matches!(kind, DefKind::Fn | DefKind::AssocFn | DefKind::Closure) {
return None;
}
if ignore_unused_generics && tcx.generics_of(def_id).requires_monomorphization(tcx) {
return None;
}
Some(local_def_id.to_def_id())
})
.collect();
let eligible_def_ids = tcx.mir_keys(()).iter().filter_map(|local_def_id| {
let def_id = local_def_id.to_def_id();
let kind = tcx.def_kind(def_id);
// `mir_keys` will give us `DefId`s for all kinds of things, not
// just "functions", like consts, statics, etc. Filter those out.
// If `ignore_unused_generics` was specified, filter out any
// generic functions from consideration as well.
if !matches!(kind, DefKind::Fn | DefKind::AssocFn | DefKind::Closure) {
return None;
}
if ignore_unused_generics && tcx.generics_of(def_id).requires_monomorphization(tcx) {
return None;
}
Some(local_def_id.to_def_id())
});
let codegenned_def_ids = codegenned_and_inlined_items(tcx);
// For each `DefId` that should have coverage instrumentation but wasn't
// codegenned, add it to the function coverage map as an unused function.
for def_id in eligible_def_ids.into_iter().filter(|id| !codegenned_def_ids.contains(id)) {
for def_id in eligible_def_ids.filter(|id| !codegenned_def_ids.contains(id)) {
// Skip any function that didn't have coverage data added to it by the
// coverage instrumentor.
let body = tcx.instance_mir(ty::InstanceDef::Item(def_id));

View file

@ -860,10 +860,7 @@ fn report_ambiguous_associated_type(
traits with associated type `{name}`, you could use the \
fully-qualified path",
),
traits
.iter()
.map(|trait_str| format!("<Example as {trait_str}>::{name}"))
.collect::<Vec<_>>(),
traits.iter().map(|trait_str| format!("<Example as {trait_str}>::{name}")),
Applicability::HasPlaceholders,
);
}

View file

@ -2156,10 +2156,7 @@ fn report_private_fields(
err.span_suggestions(
span.shrink_to_hi().with_hi(expr_span.hi()),
"you might have meant to use an associated function to build this type",
items
.iter()
.map(|(_, name, args)| suggestion(name, *args))
.collect::<Vec<String>>(),
items.iter().map(|(_, name, args)| suggestion(name, *args)),
Applicability::MaybeIncorrect,
);
}

View file

@ -109,38 +109,12 @@ pub(crate) fn expr_into_dest(
this.cfg.goto(else_blk, source_info, join_block);
join_block.unit()
}
ExprKind::Let { expr, ref pat } => {
let scope = this.local_scope();
let (true_block, false_block) = this.in_if_then_scope(scope, expr_span, |this| {
this.lower_let_expr(block, expr, pat, scope, None, expr_span, true)
});
this.cfg.push_assign_constant(
true_block,
source_info,
destination,
ConstOperand {
span: expr_span,
user_ty: None,
const_: Const::from_bool(this.tcx, true),
},
);
this.cfg.push_assign_constant(
false_block,
source_info,
destination,
ConstOperand {
span: expr_span,
user_ty: None,
const_: Const::from_bool(this.tcx, false),
},
);
let join_block = this.cfg.start_new_block();
this.cfg.goto(true_block, source_info, join_block);
this.cfg.goto(false_block, source_info, join_block);
join_block.unit()
ExprKind::Let { .. } => {
// After desugaring, `let` expressions should only appear inside `if`
// expressions or `match` guards, possibly nested within a let-chain.
// In both cases they are specifically handled by the lowerings of
// those expressions, so this case is currently unreachable.
span_bug!(expr_span, "unexpected let expression outside of if or match-guard");
}
ExprKind::NeverToAny { source } => {
let source_expr = &this.thir[source];

View file

@ -1884,10 +1884,7 @@ fn suggest_alternative_construction_methods(
err.span_suggestions_with_style(
path_span.shrink_to_hi().with_hi(call_span.hi()),
"you might have meant to use an associated function to build this type",
items
.iter()
.map(|(_, name, len)| suggestion(name, *len))
.collect::<Vec<String>>(),
items.iter().map(|(_, name, len)| suggestion(name, *len)),
Applicability::MaybeIncorrect,
SuggestionStyle::ShowAlways,
);

View file

@ -57,7 +57,7 @@ fn clone_pending(&self) -> Vec<PredicateObligation<'tcx>> {
fn take_pending(&mut self) -> Vec<PredicateObligation<'tcx>> {
let mut obligations = mem::take(&mut self.pending);
obligations.extend(self.overflowed.drain(..));
obligations.append(&mut self.overflowed);
obligations
}

View file

@ -183,6 +183,7 @@ fn cause(&self) -> Option<&dyn Error> {
#[allow(unused_variables)]
fn provide<'a>(&'a self, request: &mut Request<'a>) {}
}
mod private {
// This is a hack to prevent `type_id` from being overridden by `Error`
// implementations, since that can enable unsound downcasting.

View file

@ -2071,11 +2071,16 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// as all other references. This macro can create a raw pointer *without* creating
/// a reference first.
///
/// The `expr` in `addr_of!(expr)` is evaluated as a place expression, but never loads
/// from the place or requires the place to be dereferenceable. This means that
/// `addr_of!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
/// Note however that `addr_of!((*ptr).field)` still requires the projection to
/// `field` to be in-bounds, using the same rules as [`offset`].
/// See [`addr_of_mut`] for how to create a pointer to uninitialized data.
/// Doing that with `addr_of` would not make much sense since one could only
/// read the data, and that would be Undefined Behavior.
///
/// # Safety
///
/// The `expr` in `addr_of!(expr)` is evaluated as a place expression, but never loads from the
/// place or requires the place to be dereferenceable. This means that `addr_of!((*ptr).field)`
/// still requires the projection to `field` to be in-bounds, using the same rules as [`offset`].
/// However, `addr_of!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
///
/// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside
/// `addr_of!` like everywhere else, in which case a reference is created to call `Deref::deref` or
@ -2086,6 +2091,8 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///
/// # Example
///
/// **Correct usage: Creating a pointer to unaligned data**
///
/// ```
/// use std::ptr;
///
@ -2101,9 +2108,27 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// assert_eq!(unsafe { raw_f2.read_unaligned() }, 2);
/// ```
///
/// See [`addr_of_mut`] for how to create a pointer to uninitialized data.
/// Doing that with `addr_of` would not make much sense since one could only
/// read the data, and that would be Undefined Behavior.
/// **Incorrect usage: Out-of-bounds fields projection**
///
/// ```rust,no_run
/// use std::ptr;
///
/// #[repr(C)]
/// struct MyStruct {
/// field1: i32,
/// field2: i32,
/// }
///
/// let ptr: *const MyStruct = ptr::null();
/// let fieldptr = unsafe { ptr::addr_of!((*ptr).field2) }; // Undefined Behavior ⚠️
/// ```
///
/// The field projection `.field2` would offset the pointer by 4 bytes,
/// but the pointer is not in-bounds of an allocation for 4 bytes,
/// so this offset is Undefined Behavior.
/// See the [`offset`] docs for a full list of requirements for inbounds pointer arithmetic; the
/// same requirements apply to field projections, even inside `addr_of!`. (In particular, it makes
/// no difference whether the pointer is null or dangling.)
#[stable(feature = "raw_ref_macros", since = "1.51.0")]
#[rustc_macro_transparency = "semitransparent"]
#[allow_internal_unstable(raw_ref_op)]
@ -2120,11 +2145,12 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// as all other references. This macro can create a raw pointer *without* creating
/// a reference first.
///
/// The `expr` in `addr_of_mut!(expr)` is evaluated as a place expression, but never loads
/// from the place or requires the place to be dereferenceable. This means that
/// `addr_of_mut!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
/// Note however that `addr_of_mut!((*ptr).field)` still requires the projection to
/// `field` to be in-bounds, using the same rules as [`offset`].
/// # Safety
///
/// The `expr` in `addr_of_mut!(expr)` is evaluated as a place expression, but never loads from the
/// place or requires the place to be dereferenceable. This means that `addr_of_mut!((*ptr).field)`
/// still requires the projection to `field` to be in-bounds, using the same rules as [`offset`].
/// However, `addr_of_mut!(*ptr)` is defined behavior even if `ptr` is null, dangling, or misaligned.
///
/// Note that `Deref`/`Index` coercions (and their mutable counterparts) are applied inside
/// `addr_of_mut!` like everywhere else, in which case a reference is created to call `Deref::deref`
@ -2135,7 +2161,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
///
/// # Examples
///
/// **Creating a pointer to unaligned data:**
/// **Correct usage: Creating a pointer to unaligned data**
///
/// ```
/// use std::ptr;
@ -2153,7 +2179,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// assert_eq!({packed.f2}, 42); // `{...}` forces copying the field instead of creating a reference.
/// ```
///
/// **Creating a pointer to uninitialized data:**
/// **Correct usage: Creating a pointer to uninitialized data**
///
/// ```rust
/// use std::{ptr, mem::MaybeUninit};
@ -2169,6 +2195,28 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
/// unsafe { f1_ptr.write(true); }
/// let init = unsafe { uninit.assume_init() };
/// ```
///
/// **Incorrect usage: Out-of-bounds fields projection**
///
/// ```rust,no_run
/// use std::ptr;
///
/// #[repr(C)]
/// struct MyStruct {
/// field1: i32,
/// field2: i32,
/// }
///
/// let ptr: *mut MyStruct = ptr::null_mut();
/// let fieldptr = unsafe { ptr::addr_of_mut!((*ptr).field2) }; // Undefined Behavior ⚠️
/// ```
///
/// The field projection `.field2` would offset the pointer by 4 bytes,
/// but the pointer is not in-bounds of an allocation for 4 bytes,
/// so this offset is Undefined Behavior.
/// See the [`offset`] docs for a full list of requirements for inbounds pointer arithmetic; the
/// same requirements apply to field projections, even inside `addr_of_mut!`. (In particular, it
/// makes no difference whether the pointer is null or dangling.)
#[stable(feature = "raw_ref_macros", since = "1.51.0")]
#[rustc_macro_transparency = "semitransparent"]
#[allow_internal_unstable(raw_ref_op)]

View file

@ -9,6 +9,9 @@
pub mod locks;
pub mod os_str;
pub mod path;
#[allow(dead_code)]
#[allow(unused_imports)]
pub mod thread_local;
// FIXME(117276): remove this, move feature implementations into individual
// submodules.

View file

@ -12,8 +12,6 @@
pub mod alloc;
pub mod small_c_string;
#[allow(unused_imports)]
pub mod thread_local;
#[cfg(test)]
mod tests;

View file

@ -2,7 +2,7 @@
use super::abi;
use super::thread_local_dtor::run_dtors;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::mem;
use crate::num::NonZero;
@ -71,6 +71,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
#[inline]
pub fn sleep(dur: Duration) {
unsafe {

View file

@ -8,7 +8,7 @@
};
use crate::{
cell::UnsafeCell,
ffi::CStr,
ffi::{CStr, CString},
hint, io,
mem::ManuallyDrop,
num::NonZero,
@ -204,6 +204,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(dur: Duration) {
for timeout in dur2reltims(dur) {
expect_success(unsafe { abi::dly_tsk(timeout) }, &"dly_tsk");

View file

@ -1,6 +1,6 @@
#![cfg_attr(test, allow(dead_code))] // why is this necessary?
use super::unsupported;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::num::NonZero;
use crate::time::Duration;
@ -133,6 +133,10 @@ pub fn set_name(_name: &CStr) {
// which succeeds as-is with the SGX target.
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(dur: Duration) {
usercalls::wait_timeout(0, dur, || true);
}

View file

@ -1,7 +1,7 @@
use core::convert::TryInto;
use crate::cmp;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::mem;
use crate::num::NonZero;
@ -101,6 +101,10 @@ pub fn set_name(_name: &CStr) {
// contact the teeos rustzone team.
}
pub fn get_name() -> Option<CString> {
None
}
/// only main thread could wait for sometime in teeos
pub fn sleep(dur: Duration) {
let sleep_millis = dur.as_millis();

View file

@ -1,5 +1,5 @@
use super::unsupported;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::num::NonZero;
use crate::ptr::NonNull;
@ -23,6 +23,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(dur: Duration) {
let boot_services: NonNull<r_efi::efi::BootServices> =
crate::os::uefi::env::boot_services().expect("can't sleep").cast();

View file

@ -1,5 +1,5 @@
use crate::cmp;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::mem;
use crate::num::NonZero;
@ -225,6 +225,44 @@ pub fn set_name(_name: &CStr) {
// Newlib, Emscripten, and VxWorks have no way to set a thread name.
}
#[cfg(target_os = "linux")]
pub fn get_name() -> Option<CString> {
const TASK_COMM_LEN: usize = 16;
let mut name = vec![0u8; TASK_COMM_LEN];
let res = unsafe {
libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
};
if res != 0 {
return None;
}
name.truncate(name.iter().position(|&c| c == 0)?);
CString::new(name).ok()
}
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "tvos", target_os = "watchos"))]
pub fn get_name() -> Option<CString> {
let mut name = vec![0u8; libc::MAXTHREADNAMESIZE];
let res = unsafe {
libc::pthread_getname_np(libc::pthread_self(), name.as_mut_ptr().cast(), name.len())
};
if res != 0 {
return None;
}
name.truncate(name.iter().position(|&c| c == 0)?);
CString::new(name).ok()
}
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos"
)))]
pub fn get_name() -> Option<CString> {
None
}
#[cfg(not(target_os = "espidf"))]
pub fn sleep(dur: Duration) {
let mut secs = dur.as_secs();

View file

@ -1,5 +1,5 @@
use super::unsupported;
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::num::NonZero;
use crate::time::Duration;
@ -22,6 +22,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(_dur: Duration) {
panic!("can't sleep");
}

View file

@ -1,4 +1,4 @@
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::mem;
use crate::num::NonZero;
@ -134,6 +134,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(dur: Duration) {
let nanos = dur.as_nanos();
assert!(nanos <= u64::MAX as u128);

View file

@ -344,6 +344,12 @@ pub fn SetThreadDescription(hthread: HANDLE, lpthreaddescription: PCWSTR) -> HRE
SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); E_NOTIMPL
}
// >= Win10 1607
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreaddescription
pub fn GetThreadDescription(hthread: HANDLE, lpthreaddescription: *mut PWSTR) -> HRESULT {
SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); E_NOTIMPL
}
// >= Win8 / Server 2012
// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
pub fn GetSystemTimePreciseAsFileTime(lpsystemtimeasfiletime: *mut FILETIME) -> () {

View file

@ -1923,6 +1923,7 @@ Windows.Win32.Foundation.HANDLE_FLAG_INHERIT
Windows.Win32.Foundation.HANDLE_FLAG_PROTECT_FROM_CLOSE
Windows.Win32.Foundation.HANDLE_FLAGS
Windows.Win32.Foundation.HMODULE
Windows.Win32.Foundation.LocalFree
Windows.Win32.Foundation.MAX_PATH
Windows.Win32.Foundation.NO_ERROR
Windows.Win32.Foundation.NTSTATUS

View file

@ -379,6 +379,10 @@ pub fn InitializeProcThreadAttributeList(
) -> BOOL;
}
#[link(name = "kernel32")]
extern "system" {
pub fn LocalFree(hmem: HLOCAL) -> HLOCAL;
}
#[link(name = "kernel32")]
extern "system" {
pub fn MoveFileExW(
lpexistingfilename: PCWSTR,
@ -3441,6 +3445,7 @@ pub const fn from_u128(uuid: u128) -> Self {
pub const HANDLE_FLAG_INHERIT: HANDLE_FLAGS = 1u32;
pub const HANDLE_FLAG_PROTECT_FROM_CLOSE: HANDLE_FLAGS = 2u32;
pub const HIGH_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 128u32;
pub type HLOCAL = *mut ::core::ffi::c_void;
pub type HMODULE = *mut ::core::ffi::c_void;
pub type HRESULT = i32;
pub const IDLE_PRIORITY_CLASS: PROCESS_CREATION_FLAGS = 64u32;

View file

@ -9,7 +9,7 @@
use crate::sys::stack_overflow;
use crate::sys_common::FromInner;
use crate::time::Duration;
use alloc::ffi::CString;
use core::ffi::c_void;
use super::time::WaitableTimer;
@ -71,6 +71,29 @@ pub fn set_name(name: &CStr) {
};
}
pub fn get_name() -> Option<CString> {
unsafe {
let mut ptr = core::ptr::null_mut();
let result = c::GetThreadDescription(c::GetCurrentThread(), &mut ptr);
if result < 0 {
return None;
}
let name = String::from_utf16_lossy({
let mut len = 0;
while *ptr.add(len) != 0 {
len += 1;
}
core::slice::from_raw_parts(ptr, len)
})
.into_bytes();
// Attempt to free the memory.
// This should never fail but if it does then there's not much we can do about it.
let result = c::LocalFree(ptr.cast::<c_void>());
debug_assert!(result.is_null());
if name.is_empty() { None } else { Some(CString::from_vec_unchecked(name)) }
}
}
pub fn join(self) {
let rc = unsafe { c::WaitForSingleObject(self.handle.as_raw_handle(), c::INFINITE) };
if rc == c::WAIT_FAILED {

View file

@ -1,4 +1,4 @@
use crate::ffi::CStr;
use crate::ffi::{CStr, CString};
use crate::io;
use crate::num::NonZero;
use crate::os::xous::ffi::{
@ -113,6 +113,10 @@ pub fn set_name(_name: &CStr) {
// nope
}
pub fn get_name() -> Option<CString> {
None
}
pub fn sleep(dur: Duration) {
// Because the sleep server works on units of `usized milliseconds`, split
// the messages up into these chunks. This means we may run into issues

View file

@ -1,6 +1,7 @@
#![allow(dead_code)] // stack_guard isn't used right now on all platforms
use crate::cell::OnceCell;
use crate::sys;
use crate::sys::thread::guard::Guard;
use crate::thread::Thread;
@ -23,7 +24,8 @@ fn with<R, F>(f: F) -> Option<R>
{
THREAD_INFO
.try_with(move |thread_info| {
let thread = thread_info.thread.get_or_init(|| Thread::new(None));
let thread =
thread_info.thread.get_or_init(|| Thread::new(sys::thread::Thread::get_name()));
f(thread, &thread_info.stack_guard)
})
.ok()

View file

@ -205,7 +205,7 @@
#[doc(hidden)]
#[unstable(feature = "thread_local_internals", issue = "none")]
pub mod local_impl {
pub use crate::sys::common::thread_local::{thread_local_inner, Key, abort_on_dtor_unwind};
pub use crate::sys::thread_local::{thread_local_inner, Key, abort_on_dtor_unwind};
}
}
}

View file

@ -69,6 +69,25 @@ fn test_named_thread_truncation() {
result.unwrap().join().unwrap();
}
#[cfg(any(
target_os = "windows",
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos"
))]
#[test]
fn test_get_os_named_thread() {
use crate::sys::thread::Thread;
let handler = thread::spawn(|| {
let name = c"test me please";
Thread::set_name(name);
assert_eq!(name, Thread::get_name().unwrap().as_c_str());
});
handler.join().unwrap();
}
#[test]
#[should_panic]
fn test_invalid_named_thread() {

View file

@ -1680,7 +1680,7 @@ fn doc_impl_item(
write!(
&mut doc_buffer,
"{}",
document_short(item, cx, link, parent, rendering_params.show_def_docs,)
document_short(item, cx, link, parent, rendering_params.show_def_docs)
);
}
}
@ -2043,15 +2043,13 @@ pub(crate) fn render_impl_summary(
w.write_str("</h3>");
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\">\
<div class=\"stab portability\">{portability}</div>\
</span>",
);
}
if is_trait && let Some(portability) = portability(&i.impl_item, Some(parent)) {
write!(
w,
"<span class=\"item-info\">\
<div class=\"stab portability\">{portability}</div>\
</span>",
);
}
w.write_str("</section>");

View file

@ -33,6 +33,7 @@
};
use crate::html::layout::Page;
use crate::html::markdown::{HeadingOffset, MarkdownSummaryLine};
use crate::html::render::{document_full, document_item_info};
use crate::html::url_parts_builder::UrlPartsBuilder;
use crate::html::{highlight, static_files};
@ -818,8 +819,10 @@ fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::
info!("Documenting {name} on {ty_name:?}", ty_name = t.name);
let item_type = m.type_();
let id = cx.derive_id(format!("{item_type}.{name}"));
let mut content = Buffer::empty_from(w);
write!(&mut content, "{}", document(cx, m, Some(t), HeadingOffset::H5));
write!(content, "{}", document_full(m, cx, HeadingOffset::H5));
let toggled = !content.is_empty();
if toggled {
let method_toggle_class = if item_type.is_method() { " method-toggle" } else { "" };
@ -836,8 +839,8 @@ fn trait_item(w: &mut Buffer, cx: &mut Context<'_>, m: &clean::Item, t: &clean::
cx,
RenderMode::Normal,
);
w.write_str("</h4>");
w.write_str("</section>");
w.write_str("</h4></section>");
document_item_info(cx, m, Some(t)).render_into(w).unwrap();
if toggled {
write!(w, "</summary>");
w.push_buffer(content);

View file

@ -0,0 +1,24 @@
// This is a regression test for <https://github.com/rust-lang/rust/issues/121772>.
// The goal is to ensure that the item information is always part of the `<summary>`
// if there is one.
#![crate_name = "foo"]
#![feature(staged_api)]
#![unstable(feature = "test", issue = "none")]
// @has 'foo/trait.Foo.html'
#[stable(feature = "rust2", since = "2.2.2")]
pub trait Foo {
// @has - '//div[@class="methods"]/span[@class="item-info"]' 'bla'
// Should not be in a `<details>` because there is no doc.
#[unstable(feature = "bla", reason = "bla", issue = "111")]
fn bla() {}
// @has - '//details[@class="toggle method-toggle"]/summary/span[@class="item-info"]' 'bar'
// Should have a `<summary>` in the `<details>` containing the unstable info.
/// doc
#[unstable(feature = "bar", reason = "bla", issue = "222")]
fn bar() {}
}