Auto merge of #144814 - samueltardieu:rollup-qyum1hj, r=samueltardieu

Rollup of 17 pull requests

Successful merges:

 - rust-lang/rust#132748 (get rid of some false negatives in rustdoc::broken_intra_doc_links)
 - rust-lang/rust#143360 (loop match: error on `#[const_continue]` outside `#[loop_match]`)
 - rust-lang/rust#143662 ([rustdoc] Display unsafe attrs with edition 2024 `unsafe()` wrappers.)
 - rust-lang/rust#143771 (Constify some more `Result` functions)
 - rust-lang/rust#144185 (Document guarantees of poisoning)
 - rust-lang/rust#144395 (update fortanix tests)
 - rust-lang/rust#144478 (Improve formatting of doc code blocks)
 - rust-lang/rust#144614 (Fortify RemoveUnneededDrops test.)
 - rust-lang/rust#144703 ([test][AIX] ignore extern_weak linkage test)
 - rust-lang/rust#144747 (compiletest: Improve diagnostics for line annotation mismatches 2)
 - rust-lang/rust#144756 (detect infinite recursion with tail calls in ctfe)
 - rust-lang/rust#144766 (Add human readable name "Cygwin")
 - rust-lang/rust#144782 (Properly pass path to staged `rustc` to `compiletest` self-tests)
 - rust-lang/rust#144786 (Cleanup the definition of `group_type`)
 - rust-lang/rust#144796 (Add my previous commit name to .mailmap)
 - rust-lang/rust#144797 (Update safety comment for new_unchecked in niche_types)
 - rust-lang/rust#144803 (rustc-dev-guide subtree update)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-08-02 23:49:08 +00:00
commit 5b9564a189
92 changed files with 1500 additions and 523 deletions

View file

@ -597,6 +597,7 @@ Sam Radhakrishnan <sk09idm@gmail.com>
Samuel Tardieu <sam@rfc1149.net>
Santiago Pastorino <spastorino@gmail.com>
Santiago Pastorino <spastorino@gmail.com> <santiago@wyeworks.com>
Sasha Pourcelot <sasha.pourcelot@protonmail.com> Sasha <sasha.pourcelot@protonmail.com>
Scott McMurray <scottmcm@users.noreply.github.com>
Scott McMurray <scottmcm@users.noreply.github.com> <smcmurray@acm.org>
Scott Olson <scott@solson.me> Scott Olson <scott@scott-olson.org>

View file

@ -62,15 +62,23 @@ use crate::attributes::{AttributeParser as _, Combine, Single, WithoutArgs};
use crate::parser::{ArgParser, MetaItemParser, PathParser};
use crate::session_diagnostics::{AttributeParseError, AttributeParseErrorReason, UnknownMetaItem};
macro_rules! group_type {
($stage: ty) => {
LazyLock<(
BTreeMap<&'static [Symbol], Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $stage>, &ArgParser<'a>) + Send + Sync>)>>,
Vec<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $stage>) -> Option<AttributeKind>>>
)>
};
type GroupType<S> = LazyLock<GroupTypeInner<S>>;
struct GroupTypeInner<S: Stage> {
accepters: BTreeMap<&'static [Symbol], Vec<GroupTypeInnerAccept<S>>>,
finalizers: Vec<FinalizeFn<S>>,
}
struct GroupTypeInnerAccept<S: Stage> {
template: AttributeTemplate,
accept_fn: AcceptFn<S>,
}
type AcceptFn<S> =
Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, S>, &ArgParser<'a>) + Send + Sync>;
type FinalizeFn<S> =
Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, S>) -> Option<AttributeKind>>;
macro_rules! attribute_parsers {
(
pub(crate) static $name: ident = [$($names: ty),* $(,)?];
@ -93,11 +101,11 @@ macro_rules! attribute_parsers {
}
};
(
@[$ty: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?];
@[$stage: ty] pub(crate) static $name: ident = [$($names: ty),* $(,)?];
) => {
pub(crate) static $name: group_type!($ty) = LazyLock::new(|| {
let mut accepts = BTreeMap::<_, Vec<(AttributeTemplate, Box<dyn for<'sess, 'a> Fn(&mut AcceptContext<'_, 'sess, $ty>, &ArgParser<'a>) + Send + Sync>)>>::new();
let mut finalizes = Vec::<Box<dyn Send + Sync + Fn(&mut FinalizeContext<'_, '_, $ty>) -> Option<AttributeKind>>>::new();
pub(crate) static $name: GroupType<$stage> = LazyLock::new(|| {
let mut accepts = BTreeMap::<_, Vec<GroupTypeInnerAccept<$stage>>>::new();
let mut finalizes = Vec::<FinalizeFn<$stage>>::new();
$(
{
thread_local! {
@ -105,11 +113,14 @@ macro_rules! attribute_parsers {
};
for (path, template, accept_fn) in <$names>::ATTRIBUTES {
accepts.entry(*path).or_default().push((*template, Box::new(|cx, args| {
STATE_OBJECT.with_borrow_mut(|s| {
accept_fn(s, cx, args)
accepts.entry(*path).or_default().push(GroupTypeInnerAccept {
template: *template,
accept_fn: Box::new(|cx, args| {
STATE_OBJECT.with_borrow_mut(|s| {
accept_fn(s, cx, args)
})
})
})));
});
}
finalizes.push(Box::new(|cx| {
@ -119,7 +130,7 @@ macro_rules! attribute_parsers {
}
)*
(accepts, finalizes)
GroupTypeInner { accepters:accepts, finalizers:finalizes }
});
};
}
@ -215,7 +226,7 @@ pub trait Stage: Sized + 'static + Sealed {
type Id: Copy;
const SHOULD_EMIT_LINTS: bool;
fn parsers() -> &'static group_type!(Self);
fn parsers() -> &'static GroupType<Self>;
fn emit_err<'sess>(
&self,
@ -230,7 +241,7 @@ impl Stage for Early {
type Id = NodeId;
const SHOULD_EMIT_LINTS: bool = false;
fn parsers() -> &'static group_type!(Self) {
fn parsers() -> &'static GroupType<Self> {
&early::ATTRIBUTE_PARSERS
}
fn emit_err<'sess>(
@ -252,7 +263,7 @@ impl Stage for Late {
type Id = HirId;
const SHOULD_EMIT_LINTS: bool = true;
fn parsers() -> &'static group_type!(Self) {
fn parsers() -> &'static GroupType<Self> {
&late::ATTRIBUTE_PARSERS
}
fn emit_err<'sess>(
@ -811,8 +822,8 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
let args = parser.args();
let parts = path.segments().map(|i| i.name).collect::<Vec<_>>();
if let Some(accepts) = S::parsers().0.get(parts.as_slice()) {
for (template, accept) in accepts {
if let Some(accepts) = S::parsers().accepters.get(parts.as_slice()) {
for accept in accepts {
let mut cx: AcceptContext<'_, 'sess, S> = AcceptContext {
shared: SharedContext {
cx: self,
@ -821,11 +832,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
emit_lint: &mut emit_lint,
},
attr_span: lower_span(attr.span),
template,
template: &accept.template,
attr_path: path.get_attribute_path(),
};
accept(&mut cx, args)
(accept.accept_fn)(&mut cx, args)
}
} else {
// If we're here, we must be compiling a tool attribute... Or someone
@ -856,7 +867,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
}
let mut parsed_attributes = Vec::new();
for f in &S::parsers().1 {
for f in &S::parsers().finalizers {
if let Some(attr) = f(&mut FinalizeContext {
shared: SharedContext {
cx: self,
@ -877,7 +888,7 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> {
/// Returns whether there is a parser for an attribute with this name
pub fn is_parsed_attribute(path: &[Symbol]) -> bool {
Late::parsers().0.contains_key(path)
Late::parsers().accepters.contains_key(path)
}
fn lower_attr_args(&self, args: &ast::AttrArgs, lower_span: impl Fn(Span) -> Span) -> AttrArgs {

View file

@ -417,7 +417,7 @@ impl<'tcx> RegionInferenceContext<'tcx> {
/// minimum values.
///
/// For example:
/// ```
/// ```ignore (illustrative)
/// fn foo<'a, 'b>( /* ... */ ) where 'a: 'b { /* ... */ }
/// ```
/// would initialize two variables like so:

View file

@ -360,7 +360,7 @@ impl DefKind {
/// For example, everything prefixed with `/* Res */` in this example has
/// an associated `Res`:
///
/// ```
/// ```ignore (illustrative)
/// fn str_to_string(s: & /* Res */ str) -> /* Res */ String {
/// /* Res */ String::from(/* Res */ s)
/// }
@ -421,7 +421,7 @@ pub enum Res<Id = hir::HirId> {
/// }
///
/// impl Foo for Bar {
/// fn foo() -> Box<Self> { // SelfTyAlias
/// fn foo() -> Box<Self /* SelfTyAlias */> {
/// let _: Self; // SelfTyAlias
///
/// todo!()

View file

@ -3016,7 +3016,7 @@ impl fmt::Display for LoopIdError {
}
}
#[derive(Copy, Clone, Debug, HashStable_Generic)]
#[derive(Copy, Clone, Debug, PartialEq, HashStable_Generic)]
pub struct Destination {
/// This is `Some(_)` iff there is an explicit user-specified 'label
pub label: Option<Label>,

View file

@ -425,7 +425,7 @@ impl<'tcx> TypeFolder<TyCtxt<'tcx>> for RemapLateParam<'tcx> {
///
/// trait Foo {
/// fn bar() -> impl Deref<Target = impl Sized>;
/// // ^- RPITIT #1 ^- RPITIT #2
/// // ^- RPITIT #1 ^- RPITIT #2
/// }
///
/// impl Foo for () {

View file

@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::fmt;
use Context::*;
use rustc_ast::Label;
use rustc_hir as hir;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::def::DefKind;
@ -42,8 +41,8 @@ enum Context {
ConstBlock,
/// E.g. `#[loop_match] loop { state = 'label: { /* ... */ } }`.
LoopMatch {
/// The label of the labeled block (not of the loop itself).
labeled_block: Label,
/// The destination pointing to the labeled block (not to the loop itself).
labeled_block: Destination,
},
}
@ -186,18 +185,18 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
{
self.with_context(UnlabeledBlock(b.span.shrink_to_lo()), |v| v.visit_block(b));
}
hir::ExprKind::Break(break_label, ref opt_expr) => {
hir::ExprKind::Break(break_destination, ref opt_expr) => {
if let Some(e) = opt_expr {
self.visit_expr(e);
}
if self.require_label_in_labeled_block(e.span, &break_label, "break") {
if self.require_label_in_labeled_block(e.span, &break_destination, "break") {
// If we emitted an error about an unlabeled break in a labeled
// block, we don't need any further checking for this break any more
return;
}
let loop_id = match break_label.target_id {
let loop_id = match break_destination.target_id {
Ok(loop_id) => Some(loop_id),
Err(hir::LoopIdError::OutsideLoopScope) => None,
Err(hir::LoopIdError::UnlabeledCfInWhileCondition) => {
@ -212,18 +211,25 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
// A `#[const_continue]` must break to a block in a `#[loop_match]`.
if find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::ConstContinue(_)) {
if let Some(break_label) = break_label.label {
let is_target_label = |cx: &Context| match cx {
Context::LoopMatch { labeled_block } => {
break_label.ident.name == labeled_block.ident.name
}
_ => false,
};
let Some(label) = break_destination.label else {
let span = e.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
};
if !self.cx_stack.iter().rev().any(is_target_label) {
let span = break_label.ident.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
let is_target_label = |cx: &Context| match cx {
Context::LoopMatch { labeled_block } => {
// NOTE: with macro expansion, the label's span might be different here
// even though it does still refer to the same HIR node. A block
// can't have two labels, so the hir_id is a unique identifier.
assert!(labeled_block.target_id.is_ok()); // see `is_loop_match`.
break_destination.target_id == labeled_block.target_id
}
_ => false,
};
if !self.cx_stack.iter().rev().any(is_target_label) {
let span = label.ident.span;
self.tcx.dcx().emit_fatal(ConstContinueBadLabel { span });
}
}
@ -249,7 +255,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
Some(kind) => {
let suggestion = format!(
"break{}",
break_label
break_destination
.label
.map_or_else(String::new, |l| format!(" {}", l.ident))
);
@ -259,7 +265,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
kind: kind.name(),
suggestion,
loop_label,
break_label: break_label.label,
break_label: break_destination.label,
break_expr_kind: &break_expr.kind,
break_expr_span: break_expr.span,
});
@ -268,7 +274,7 @@ impl<'hir> Visitor<'hir> for CheckLoopVisitor<'hir> {
}
let sp_lo = e.span.with_lo(e.span.lo() + BytePos("break".len() as u32));
let label_sp = match break_label.label {
let label_sp = match break_destination.label {
Some(label) => sp_lo.with_hi(label.ident.span.hi()),
None => sp_lo.shrink_to_lo(),
};
@ -416,7 +422,7 @@ impl<'hir> CheckLoopVisitor<'hir> {
&self,
e: &'hir hir::Expr<'hir>,
body: &'hir hir::Block<'hir>,
) -> Option<Label> {
) -> Option<Destination> {
if !find_attr!(self.tcx.hir_attrs(e.hir_id), AttributeKind::LoopMatch(_)) {
return None;
}
@ -438,8 +444,8 @@ impl<'hir> CheckLoopVisitor<'hir> {
let hir::ExprKind::Assign(_, rhs_expr, _) = loop_body_expr.kind else { return None };
let hir::ExprKind::Block(_, label) = rhs_expr.kind else { return None };
let hir::ExprKind::Block(block, label) = rhs_expr.kind else { return None };
label
Some(Destination { label, target_id: Ok(block.hir_id) })
}
}

View file

@ -2391,13 +2391,11 @@ fn migration_suggestion_for_2229(
/// let mut p = Point { x: 10, y: 10 };
///
/// let c = || {
/// p.x += 10;
/// // ^ E1 ^
/// p.x += 10; // E1
/// // ...
/// // More code
/// // ...
/// p.x += 10; // E2
/// // ^ E2 ^
/// };
/// ```
/// `CaptureKind` associated with both `E1` and `E2` will be ByRef(MutBorrow),

View file

@ -36,7 +36,7 @@
//! fn bar<T>(a: T, b: impl for<'a> Fn(&'a T)) {}
//! fn foo<T>(x: T) {
//! bar(x, |y| { /* ... */})
//! // ^ closure arg
//! // ^ closure arg
//! }
//! ```
//!

View file

@ -533,8 +533,10 @@ impl<'tcx> TyCtxt<'tcx> {
/// ```
/// fn foo(x: usize) -> bool {
/// if x == 1 {
/// true // If `get_fn_id_for_return_block` gets passed the `id` corresponding
/// } else { // to this, it will return `foo`'s `HirId`.
/// // If `get_fn_id_for_return_block` gets passed the `id` corresponding to this, it
/// // will return `foo`'s `HirId`.
/// true
/// } else {
/// false
/// }
/// }
@ -543,8 +545,10 @@ impl<'tcx> TyCtxt<'tcx> {
/// ```compile_fail,E0308
/// fn foo(x: usize) -> bool {
/// loop {
/// true // If `get_fn_id_for_return_block` gets passed the `id` corresponding
/// } // to this, it will return `None`.
/// // If `get_fn_id_for_return_block` gets passed the `id` corresponding to this, it
/// // will return `None`.
/// true
/// }
/// false
/// }
/// ```

View file

@ -1017,7 +1017,8 @@ pub struct LocalDecl<'tcx> {
/// ```
/// fn foo(x: &str) {
/// #[allow(unused_mut)]
/// let mut x: u32 = { // <- one unused mut
/// let mut x: u32 = {
/// //^ one unused mut
/// let mut y: u32 = x.parse().unwrap();
/// y + 2
/// };

View file

@ -87,7 +87,7 @@ mir_build_confused = missing patterns are not covered because `{$variable}` is i
mir_build_const_continue_bad_const = could not determine the target branch for this `#[const_continue]`
.label = this value is too generic
mir_build_const_continue_missing_value = a `#[const_continue]` must break to a label with a value
mir_build_const_continue_missing_label_or_value = a `#[const_continue]` must break to a label with a value
mir_build_const_continue_not_const = could not determine the target branch for this `#[const_continue]`
.help = try extracting the expression into a `const` item

View file

@ -57,7 +57,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
/// ```
/// #![feature(unsized_fn_params)]
/// # use core::fmt::Debug;
/// fn foo(_p: dyn Debug) { /* ... */ }
/// fn foo(_p: dyn Debug) {
/// /* ... */
/// }
///
/// fn bar(box_p: Box<dyn Debug>) { foo(*box_p); }
/// ```

View file

@ -1254,8 +1254,8 @@ pub(crate) struct ConstContinueBadConst {
}
#[derive(Diagnostic)]
#[diag(mir_build_const_continue_missing_value)]
pub(crate) struct ConstContinueMissingValue {
#[diag(mir_build_const_continue_missing_label_or_value)]
pub(crate) struct ConstContinueMissingLabelOrValue {
#[primary_span]
pub span: Span,
}

View file

@ -852,9 +852,9 @@ impl<'tcx> ThirBuildCx<'tcx> {
if find_attr!(self.tcx.hir_attrs(expr.hir_id), AttributeKind::ConstContinue(_)) {
match dest.target_id {
Ok(target_id) => {
let Some(value) = value else {
let (Some(value), Some(_)) = (value, dest.label) else {
let span = expr.span;
self.tcx.dcx().emit_fatal(ConstContinueMissingValue { span })
self.tcx.dcx().emit_fatal(ConstContinueMissingLabelOrValue { span })
};
ExprKind::ConstContinue {

View file

@ -108,6 +108,7 @@ impl<'tcx> MaybePlacesSwitchIntData<'tcx> {
///
/// ```rust
/// struct S;
/// #[rustfmt::skip]
/// fn foo(pred: bool) { // maybe-init:
/// // {}
/// let a = S; let mut b = S; let c; let d; // {a, b}
@ -197,6 +198,7 @@ impl<'a, 'tcx> HasMoveData<'tcx> for MaybeInitializedPlaces<'a, 'tcx> {
///
/// ```rust
/// struct S;
/// #[rustfmt::skip]
/// fn foo(pred: bool) { // maybe-uninit:
/// // {a, b, c, d}
/// let a = S; let mut b = S; let c; let d; // { c, d}
@ -289,6 +291,7 @@ impl<'tcx> HasMoveData<'tcx> for MaybeUninitializedPlaces<'_, 'tcx> {
///
/// ```rust
/// struct S;
/// #[rustfmt::skip]
/// fn foo(pred: bool) { // ever-init:
/// // { }
/// let a = S; let mut b = S; let c; let d; // {a, b }

View file

@ -18,7 +18,7 @@ impl<'tcx> crate::MirPass<'tcx> for CtfeLimit {
.basic_blocks
.iter_enumerated()
.filter_map(|(node, node_data)| {
if matches!(node_data.terminator().kind, TerminatorKind::Call { .. })
if matches!(node_data.terminator().kind, TerminatorKind::Call { .. } | TerminatorKind::TailCall { .. })
// Back edges in a CFG indicate loops
|| has_back_edge(doms, node, node_data)
{

View file

@ -611,6 +611,7 @@ where
///
/// For example, with 3 fields, the drop ladder is
///
/// ```text
/// .d0:
/// ELAB(drop location.0 [target=.d1, unwind=.c1])
/// .d1:
@ -621,8 +622,10 @@ where
/// ELAB(drop location.1 [target=.c2])
/// .c2:
/// ELAB(drop location.2 [target=`self.unwind`])
/// ```
///
/// For possible-async drops in coroutines we also need dropline ladder
/// ```text
/// .d0 (mainline):
/// ELAB(drop location.0 [target=.d1, unwind=.c1, drop=.e1])
/// .d1 (mainline):
@ -637,6 +640,7 @@ where
/// ELAB(drop location.1 [target=.e2, unwind=.c2])
/// .e2 (dropline):
/// ELAB(drop location.2 [target=`self.drop`, unwind=`self.unwind`])
/// ```
///
/// NOTE: this does not clear the master drop flag, so you need
/// to point succ/unwind on a `drop_ladder_bottom`.

View file

@ -768,7 +768,9 @@ impl SyntaxContext {
///
/// ```rust
/// #![feature(decl_macro)]
/// mod foo { pub fn f() {} } // `f`'s `SyntaxContext` is empty.
/// mod foo {
/// pub fn f() {} // `f`'s `SyntaxContext` is empty.
/// }
/// m!(f);
/// macro m($f:ident) {
/// mod bar {

View file

@ -501,9 +501,9 @@ impl<'scope> Scope<'scope> {
/// let mut value_c = None;
/// rayon::scope(|s| {
/// s.spawn(|s1| {
/// // ^ this is the same scope as `s`; this handle `s1`
/// // is intended for use by the spawned task,
/// // since scope handles cannot cross thread boundaries.
/// // ^ this is the same scope as `s`; this handle `s1`
/// // is intended for use by the spawned task,
/// // since scope handles cannot cross thread boundaries.
///
/// value_a = Some(22);
///

View file

@ -44,7 +44,7 @@ impl SleepData {
/// jobs are published, and it either blocks threads or wakes them in response to these
/// events. See the [`README.md`] in this module for more details.
///
/// [`README.md`] README.md
/// [`README.md`]: README.md
pub(super) struct Sleep {
/// One "sleep state" per worker. Used to track if a worker is sleeping and to have
/// them block.

View file

@ -265,12 +265,12 @@ use crate::vec::{self, Vec};
/// You can look at these with the [`as_ptr`], [`len`], and [`capacity`]
/// methods:
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// use std::mem;
///
/// let story = String::from("Once upon a time...");
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent automatically dropping the String's data
/// let mut story = mem::ManuallyDrop::new(story);
///
@ -970,13 +970,13 @@ impl String {
///
/// # Examples
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// use std::mem;
///
/// unsafe {
/// let s = String::from("hello");
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent automatically dropping the String's data
/// let mut s = mem::ManuallyDrop::new(s);
///

View file

@ -566,13 +566,13 @@ impl<T> Vec<T> {
///
/// # Examples
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// use std::ptr;
/// use std::mem;
///
/// let v = vec![1, 2, 3];
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
@ -674,6 +674,7 @@ impl<T> Vec<T> {
///
/// # Examples
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// #![feature(box_vec_non_null)]
///
@ -682,7 +683,6 @@ impl<T> Vec<T> {
///
/// let v = vec![1, 2, 3];
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
@ -994,6 +994,7 @@ impl<T, A: Allocator> Vec<T, A> {
///
/// # Examples
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// #![feature(allocator_api)]
///
@ -1007,7 +1008,6 @@ impl<T, A: Allocator> Vec<T, A> {
/// v.push(2);
/// v.push(3);
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
@ -1114,6 +1114,7 @@ impl<T, A: Allocator> Vec<T, A> {
///
/// # Examples
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// ```
/// #![feature(allocator_api, box_vec_non_null)]
///
@ -1127,7 +1128,6 @@ impl<T, A: Allocator> Vec<T, A> {
/// v.push(2);
/// v.push(3);
///
// FIXME Update this when vec_into_raw_parts is stabilized
/// // Prevent running `v`'s destructor so we are in complete control
/// // of the allocation.
/// let mut v = mem::ManuallyDrop::new(v);
@ -3872,8 +3872,8 @@ impl<T, A: Allocator> Vec<T, A> {
/// while i < vec.len() - end_items {
/// if some_predicate(&mut vec[i]) {
/// let val = vec.remove(i);
/// # extracted.push(val);
/// // your code here
/// # extracted.push(val);
/// } else {
/// i += 1;
/// }

View file

@ -2068,9 +2068,9 @@ impl<T: ?Sized + fmt::Display> fmt::Display for RefMut<'_, T> {
/// implies exclusive access to its `T`:
///
/// ```rust
/// #![forbid(unsafe_code)] // with exclusive accesses,
/// // `UnsafeCell` is a transparent no-op wrapper,
/// // so no need for `unsafe` here.
/// #![forbid(unsafe_code)]
/// // with exclusive accesses, `UnsafeCell` is a transparent no-op wrapper, so no need for
/// // `unsafe` here.
/// use std::cell::UnsafeCell;
///
/// let mut x: UnsafeCell<i32> = 42.into();

View file

@ -649,8 +649,6 @@ pub const fn must_use<T>(value: T) -> T {
/// }
/// }
/// ```
///
///
#[unstable(feature = "likely_unlikely", issue = "136873")]
#[inline(always)]
pub const fn likely(b: bool) -> bool {

View file

@ -233,10 +233,12 @@
//!
//! ```
//! let mut values = vec![41];
//! for x in &mut values { // same as `values.iter_mut()`
//! for x in &mut values {
//! // ^ same as `values.iter_mut()`
//! *x += 1;
//! }
//! for x in &values { // same as `values.iter()`
//! for x in &values {
//! // ^ same as `values.iter()`
//! assert_eq!(*x, 42);
//! }
//! assert_eq!(values.len(), 1);

View file

@ -807,7 +807,7 @@ pub trait Iterator {
/// might be preferable to keep a functional style with longer iterators:
///
/// ```
/// (0..5).flat_map(|x| x * 100 .. x * 110)
/// (0..5).flat_map(|x| (x * 100)..(x * 110))
/// .enumerate()
/// .filter(|&(i, x)| (i + x) % 3 == 0)
/// .for_each(|(i, x)| println!("{i}:{x}"));

View file

@ -271,7 +271,10 @@ pub macro cfg_select($($tt:tt)*) {
/// // expression given.
/// debug_assert!(true);
///
/// fn some_expensive_computation() -> bool { true } // a very simple function
/// fn some_expensive_computation() -> bool {
/// // Some expensive computation here
/// true
/// }
/// debug_assert!(some_expensive_computation());
///
/// // assert with a custom message
@ -1547,7 +1550,10 @@ pub(crate) mod builtin {
/// // expression given.
/// assert!(true);
///
/// fn some_computation() -> bool { true } // a very simple function
/// fn some_computation() -> bool {
/// // Some expensive computation here
/// true
/// }
///
/// assert!(some_computation());
///

View file

@ -138,8 +138,7 @@ unsafe impl<T: Sync + PointeeSized> Send for &T {}
/// impl Bar for Impl { }
///
/// let x: &dyn Foo = &Impl; // OK
/// // let y: &dyn Bar = &Impl; // error: the trait `Bar` cannot
/// // be made into an object
/// // let y: &dyn Bar = &Impl; // error: the trait `Bar` cannot be made into an object
/// ```
///
/// [trait object]: ../../book/ch17-02-trait-objects.html

View file

@ -773,8 +773,7 @@ impl<T> MaybeUninit<T> {
/// // Initialize the `MaybeUninit` using `Cell::set`:
/// unsafe {
/// b.assume_init_ref().set(true);
/// // ^^^^^^^^^^^^^^^
/// // Reference to an uninitialized `Cell<bool>`: UB!
/// //^^^^^^^^^^^^^^^ Reference to an uninitialized `Cell<bool>`: UB!
/// }
/// ```
#[stable(feature = "maybe_uninit_ref", since = "1.55.0")]
@ -864,9 +863,9 @@ impl<T> MaybeUninit<T> {
/// {
/// let mut buffer = MaybeUninit::<[u8; 64]>::uninit();
/// reader.read_exact(unsafe { buffer.assume_init_mut() })?;
/// // ^^^^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// // ^^^^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// Ok(unsafe { buffer.assume_init() })
/// }
/// ```
@ -884,13 +883,13 @@ impl<T> MaybeUninit<T> {
/// let foo: Foo = unsafe {
/// let mut foo = MaybeUninit::<Foo>::uninit();
/// ptr::write(&mut foo.assume_init_mut().a as *mut u32, 1337);
/// // ^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// // ^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// ptr::write(&mut foo.assume_init_mut().b as *mut u8, 42);
/// // ^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// // ^^^^^^^^^^^^^^^^^^^^^
/// // (mutable) reference to uninitialized memory!
/// // This is undefined behavior.
/// foo.assume_init()
/// };
/// ```

View file

@ -46,11 +46,11 @@ macro_rules! define_valid_range_type {
/// primitive without checking whether its zero.
///
/// # Safety
/// Immediate language UB if `val == 0`, as it violates the validity
/// invariant of this type.
/// Immediate language UB if `val` is not within the valid range for this
/// type, as it violates the validity invariant.
#[inline]
pub const unsafe fn new_unchecked(val: $int) -> Self {
// SAFETY: Caller promised that `val` is non-zero.
// SAFETY: Caller promised that `val` is within the valid range.
unsafe { $name(val) }
}

View file

@ -271,6 +271,7 @@ mod prim_bool {}
/// When the compiler sees a value of type `!` in a [coercion site], it implicitly inserts a
/// coercion to allow the type checker to infer any type:
///
// FIXME: use `core::convert::absurd` here instead, once it's merged
/// ```rust,ignore (illustrative-and-has-placeholders)
/// // this
/// let x: u8 = panic!();
@ -281,7 +282,6 @@ mod prim_bool {}
/// // where absurd is a function with the following signature
/// // (it's sound, because `!` always marks unreachable code):
/// fn absurd<T>(_: !) -> T { ... }
// FIXME: use `core::convert::absurd` here instead, once it's merged
/// ```
///
/// This can lead to compilation errors if the type cannot be inferred:

View file

@ -534,6 +534,7 @@
#![stable(feature = "rust1", since = "1.0.0")]
use crate::iter::{self, FusedIterator, TrustedLen};
use crate::marker::Destruct;
use crate::ops::{self, ControlFlow, Deref, DerefMut};
use crate::{convert, fmt, hint};
@ -606,7 +607,13 @@ impl<T, E> Result<T, E> {
#[must_use]
#[inline]
#[stable(feature = "is_some_and", since = "1.70.0")]
pub fn is_ok_and(self, f: impl FnOnce(T) -> bool) -> bool {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn is_ok_and<F>(self, f: F) -> bool
where
F: ~const FnOnce(T) -> bool + ~const Destruct,
T: ~const Destruct,
E: ~const Destruct,
{
match self {
Err(_) => false,
Ok(x) => f(x),
@ -655,7 +662,13 @@ impl<T, E> Result<T, E> {
#[must_use]
#[inline]
#[stable(feature = "is_some_and", since = "1.70.0")]
pub fn is_err_and(self, f: impl FnOnce(E) -> bool) -> bool {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn is_err_and<F>(self, f: F) -> bool
where
F: ~const FnOnce(E) -> bool + ~const Destruct,
E: ~const Destruct,
T: ~const Destruct,
{
match self {
Ok(_) => false,
Err(e) => f(e),
@ -682,8 +695,13 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
#[rustc_diagnostic_item = "result_ok_method"]
pub fn ok(self) -> Option<T> {
pub const fn ok(self) -> Option<T>
where
T: ~const Destruct,
E: ~const Destruct,
{
match self {
Ok(x) => Some(x),
Err(_) => None,
@ -706,7 +724,12 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn err(self) -> Option<E> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn err(self) -> Option<E>
where
T: ~const Destruct,
E: ~const Destruct,
{
match self {
Ok(_) => None,
Err(x) => Some(x),
@ -796,7 +819,11 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Result<U, E> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn map<U, F>(self, op: F) -> Result<U, E>
where
F: ~const FnOnce(T) -> U + ~const Destruct,
{
match self {
Ok(t) => Ok(op(t)),
Err(e) => Err(e),
@ -823,8 +850,15 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "result_map_or", since = "1.41.0")]
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
#[must_use = "if you don't need the returned value, use `if let` instead"]
pub fn map_or<U, F: FnOnce(T) -> U>(self, default: U, f: F) -> U {
pub const fn map_or<U, F>(self, default: U, f: F) -> U
where
F: ~const FnOnce(T) -> U + ~const Destruct,
T: ~const Destruct,
E: ~const Destruct,
U: ~const Destruct,
{
match self {
Ok(t) => f(t),
Err(_) => default,
@ -851,7 +885,12 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "result_map_or_else", since = "1.41.0")]
pub fn map_or_else<U, D: FnOnce(E) -> U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn map_or_else<U, D, F>(self, default: D, f: F) -> U
where
D: ~const FnOnce(E) -> U + ~const Destruct,
F: ~const FnOnce(T) -> U + ~const Destruct,
{
match self {
Ok(t) => f(t),
Err(e) => default(e),
@ -877,10 +916,13 @@ impl<T, E> Result<T, E> {
/// [default value]: Default::default
#[inline]
#[unstable(feature = "result_option_map_or_default", issue = "138099")]
pub fn map_or_default<U, F>(self, f: F) -> U
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn map_or_default<U, F>(self, f: F) -> U
where
U: Default,
F: FnOnce(T) -> U,
F: ~const FnOnce(T) -> U + ~const Destruct,
U: ~const Default,
T: ~const Destruct,
E: ~const Destruct,
{
match self {
Ok(t) => f(t),
@ -908,7 +950,11 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn map_err<F, O: FnOnce(E) -> F>(self, op: O) -> Result<T, F> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn map_err<F, O>(self, op: O) -> Result<T, F>
where
O: ~const FnOnce(E) -> F + ~const Destruct,
{
match self {
Ok(t) => Ok(t),
Err(e) => Err(op(e)),
@ -930,7 +976,11 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "result_option_inspect", since = "1.76.0")]
pub fn inspect<F: FnOnce(&T)>(self, f: F) -> Self {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn inspect<F>(self, f: F) -> Self
where
F: ~const FnOnce(&T) + ~const Destruct,
{
if let Ok(ref t) = self {
f(t);
}
@ -954,7 +1004,11 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "result_option_inspect", since = "1.76.0")]
pub fn inspect_err<F: FnOnce(&E)>(self, f: F) -> Self {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn inspect_err<F>(self, f: F) -> Self
where
F: ~const FnOnce(&E) + ~const Destruct,
{
if let Err(ref e) = self {
f(e);
}
@ -1033,7 +1087,8 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn iter(&self) -> Iter<'_, T> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn iter(&self) -> Iter<'_, T> {
Iter { inner: self.as_ref().ok() }
}
@ -1056,7 +1111,8 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn iter_mut(&mut self) -> IterMut<'_, T> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn iter_mut(&mut self) -> IterMut<'_, T> {
IterMut { inner: self.as_mut().ok() }
}
@ -1195,9 +1251,11 @@ impl<T, E> Result<T, E> {
/// [`FromStr`]: crate::str::FromStr
#[inline]
#[stable(feature = "result_unwrap_or_default", since = "1.16.0")]
pub fn unwrap_or_default(self) -> T
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn unwrap_or_default(self) -> T
where
T: Default,
T: ~const Default + ~const Destruct,
E: ~const Destruct,
{
match self {
Ok(x) => x,
@ -1370,7 +1428,13 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn and<U>(self, res: Result<U, E>) -> Result<U, E>
where
T: ~const Destruct,
E: ~const Destruct,
U: ~const Destruct,
{
match self {
Ok(_) => res,
Err(e) => Err(e),
@ -1409,8 +1473,12 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
#[rustc_confusables("flat_map", "flatmap")]
pub fn and_then<U, F: FnOnce(T) -> Result<U, E>>(self, op: F) -> Result<U, E> {
pub const fn and_then<U, F>(self, op: F) -> Result<U, E>
where
F: ~const FnOnce(T) -> Result<U, E> + ~const Destruct,
{
match self {
Ok(t) => op(t),
Err(e) => Err(e),
@ -1446,7 +1514,13 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn or<F>(self, res: Result<T, F>) -> Result<T, F> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn or<F>(self, res: Result<T, F>) -> Result<T, F>
where
T: ~const Destruct,
E: ~const Destruct,
F: ~const Destruct,
{
match self {
Ok(v) => Ok(v),
Err(_) => res,
@ -1471,7 +1545,11 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn or_else<F, O: FnOnce(E) -> Result<T, F>>(self, op: O) -> Result<T, F> {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn or_else<F, O>(self, op: O) -> Result<T, F>
where
O: ~const FnOnce(E) -> Result<T, F> + ~const Destruct,
{
match self {
Ok(t) => Ok(t),
Err(e) => op(e),
@ -1498,7 +1576,12 @@ impl<T, E> Result<T, E> {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn unwrap_or(self, default: T) -> T {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn unwrap_or(self, default: T) -> T
where
T: ~const Destruct,
E: ~const Destruct,
{
match self {
Ok(t) => t,
Err(_) => default,
@ -1519,7 +1602,11 @@ impl<T, E> Result<T, E> {
#[inline]
#[track_caller]
#[stable(feature = "rust1", since = "1.0.0")]
pub fn unwrap_or_else<F: FnOnce(E) -> T>(self, op: F) -> T {
#[rustc_const_unstable(feature = "const_result_trait_fn", issue = "144211")]
pub const fn unwrap_or_else<F>(self, op: F) -> T
where
F: ~const FnOnce(E) -> T + ~const Destruct,
{
match self {
Ok(t) => t,
Err(e) => op(e),
@ -1544,7 +1631,7 @@ impl<T, E> Result<T, E> {
///
/// ```no_run
/// let x: Result<u32, &str> = Err("emergency failure");
/// unsafe { x.unwrap_unchecked(); } // Undefined behavior!
/// unsafe { x.unwrap_unchecked() }; // Undefined behavior!
/// ```
#[inline]
#[track_caller]
@ -1762,7 +1849,7 @@ impl<T, E> Result<Result<T, E>, E> {
#[cold]
#[track_caller]
fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! {
panic!("{msg}: {error:?}")
panic!("{msg}: {error:?}");
}
// This is a separate function to avoid constructing a `dyn Debug`
@ -1773,7 +1860,7 @@ fn unwrap_failed(msg: &str, error: &dyn fmt::Debug) -> ! {
#[inline]
#[cold]
#[track_caller]
fn unwrap_failed<T>(_msg: &str, _error: &T) -> ! {
const fn unwrap_failed<T>(_msg: &str, _error: &T) -> ! {
panic!()
}

View file

@ -20,6 +20,7 @@
#![feature(const_eval_select)]
#![feature(const_ops)]
#![feature(const_ref_cell)]
#![feature(const_result_trait_fn)]
#![feature(const_trait_impl)]
#![feature(core_float_math)]
#![feature(core_intrinsics)]
@ -82,6 +83,7 @@
#![feature(pointer_is_aligned_to)]
#![feature(portable_simd)]
#![feature(ptr_metadata)]
#![feature(result_option_map_or_default)]
#![feature(slice_from_ptr_range)]
#![feature(slice_internals)]
#![feature(slice_partition_dedup)]

View file

@ -421,3 +421,86 @@ fn result_try_trait_v2_branch() {
assert_eq!(Ok::<NonZero<u32>, ()>(one).branch(), Continue(one));
assert_eq!(Err::<NonZero<u32>, ()>(()).branch(), Break(Err(())));
}
// helper functions for const contexts
const fn eq10(x: u8) -> bool {
x == 10
}
const fn eq20(e: u8) -> bool {
e == 20
}
const fn double_u16(x: u8) -> u16 {
x as u16 * 2
}
const fn to_u16(x: u8) -> u16 {
x as u16
}
const fn err_to_u16_plus1(e: u8) -> u16 {
e as u16 + 1
}
const fn inc_u8(x: u8) -> u8 {
x + 1
}
const fn noop_u8_ref(_x: &u8) {}
const fn add1_result(x: u8) -> Result<u8, u8> {
Ok(x + 1)
}
const fn add5_result(e: u8) -> Result<u8, u8> {
Ok(e + 5)
}
const fn plus7_u8(e: u8) -> u8 {
e + 7
}
#[test]
fn test_const_result() {
const {
let r_ok: Result<u8, u8> = Ok(10);
let r_err: Result<u8, u8> = Err(20);
assert!(r_ok.is_ok());
assert!(r_err.is_err());
let ok_and = r_ok.is_ok_and(eq10);
let err_and = r_err.is_err_and(eq20);
assert!(ok_and);
assert!(err_and);
let opt_ok: Option<u8> = r_ok.ok();
let opt_err: Option<u8> = r_err.err();
assert!(opt_ok.unwrap() == 10);
assert!(opt_err.unwrap() == 20);
let mapped: Result<u16, u8> = r_ok.map(double_u16);
let map_or: u16 = r_ok.map_or(0, to_u16);
let map_or_else: u16 = r_err.map_or_else(err_to_u16_plus1, to_u16);
let map_or_default: u8 = r_err.map_or_default(inc_u8);
assert!(mapped.unwrap_or_default() == 20);
assert!(map_or == 10);
assert!(map_or_else == 21);
assert!(map_or_default == 0);
let _map_err: Result<u8, u16> = r_err.map_err(to_u16);
//FIXME: currently can't unwrap const error
// assert!(map_err.unwrap_err() == 20);
let inspected_ok: Result<u8, u8> = r_ok.inspect(noop_u8_ref);
let inspected_err: Result<u8, u8> = r_err.inspect_err(noop_u8_ref);
assert!(inspected_ok.is_ok());
assert!(inspected_err.is_err());
let unwrapped_default: u8 = r_err.unwrap_or_default();
assert!(unwrapped_default == 0);
let and_then: Result<u8, u8> = r_ok.and_then(add1_result);
let or: Result<u8, u8> = r_err.or(Ok(5));
let or_else: Result<u8, u8> = r_err.or_else(add5_result);
assert!(and_then.unwrap_or_default() == 11);
assert!(or.unwrap_or_default() == 5);
assert!(or_else.unwrap_or_default() == 25);
let u_or: u8 = r_err.unwrap_or(7);
let u_or_else: u8 = r_err.unwrap_or_else(plus7_u8);
assert!(u_or == 7);
assert!(u_or_else == 27);
};
}

View file

@ -3259,8 +3259,8 @@ impl Path {
///
/// # Examples
///
#[cfg_attr(unix, doc = "```no_run")]
#[cfg_attr(not(unix), doc = "```ignore")]
/// ```rust,no_run
/// # #[cfg(unix)] {
/// use std::path::Path;
/// use std::os::unix::fs::symlink;
///
@ -3268,6 +3268,7 @@ impl Path {
/// symlink("/origin_does_not_exist/", link_path).unwrap();
/// assert_eq!(link_path.is_symlink(), true);
/// assert_eq!(link_path.exists(), false);
/// # }
/// ```
///
/// # See Also

View file

@ -16,6 +16,8 @@ use crate::sync::Once;
/// A `OnceLock` can be thought of as a safe abstraction over uninitialized data that becomes
/// initialized once written.
///
/// Unlike [`Mutex`](crate::sync::Mutex), `OnceLock` is never poisoned on panic.
///
/// [`OnceCell`]: crate::cell::OnceCell
/// [`LazyLock<T, F>`]: crate::sync::LazyLock
/// [`LazyLock::new(|| ...)`]: crate::sync::LazyLock::new

View file

@ -2,15 +2,16 @@
//!
//! # Poisoning
//!
//! All synchronization objects in this module implement a strategy called "poisoning"
//! where if a thread panics while holding the exclusive access granted by the primitive,
//! the state of the primitive is set to "poisoned".
//! This information is then propagated to all other threads
//! All synchronization objects in this module implement a strategy called
//! "poisoning" where a primitive becomes poisoned if it recognizes that some
//! thread has panicked while holding the exclusive access granted by the
//! primitive. This information is then propagated to all other threads
//! to signify that the data protected by this primitive is likely tainted
//! (some invariant is not being upheld).
//!
//! The specifics of how this "poisoned" state affects other threads
//! depend on the primitive. See [#Overview] below.
//! The specifics of how this "poisoned" state affects other threads and whether
//! the panics are recognized reliably or on a best-effort basis depend on the
//! primitive. See [#Overview] below.
//!
//! For the alternative implementations that do not employ poisoning,
//! see [`std::sync::nonpoison`].
@ -36,14 +37,15 @@
//! - [`Mutex`]: Mutual Exclusion mechanism, which ensures that at
//! most one thread at a time is able to access some data.
//!
//! [`Mutex::lock()`] returns a [`LockResult`],
//! providing a way to deal with the poisoned state.
//! See [`Mutex`'s documentation](Mutex#poisoning) for more.
//! Panicking while holding the lock typically poisons the mutex, but it is
//! not guaranteed to detect this condition in all circumstances.
//! [`Mutex::lock()`] returns a [`LockResult`], providing a way to deal with
//! the poisoned state. See [`Mutex`'s documentation](Mutex#poisoning) for more.
//!
//! - [`Once`]: A thread-safe way to run a piece of code only once.
//! Mostly useful for implementing one-time global initialization.
//!
//! [`Once`] is poisoned if the piece of code passed to
//! [`Once`] is reliably poisoned if the piece of code passed to
//! [`Once::call_once()`] or [`Once::call_once_force()`] panics.
//! When in poisoned state, subsequent calls to [`Once::call_once()`] will panic too.
//! [`Once::call_once_force()`] can be used to clear the poisoned state.
@ -53,7 +55,7 @@
//! writer at a time. In some cases, this can be more efficient than
//! a mutex.
//!
//! This implementation, like [`Mutex`], will become poisoned on a panic.
//! This implementation, like [`Mutex`], usually becomes poisoned on a panic.
//! Note, however, that an `RwLock` may only be poisoned if a panic occurs
//! while it is locked exclusively (write mode). If a panic occurs in any reader,
//! then the lock will not be poisoned.

View file

@ -18,20 +18,69 @@ use crate::sys::sync as sys;
/// # Poisoning
///
/// The mutexes in this module implement a strategy called "poisoning" where a
/// mutex is considered poisoned whenever a thread panics while holding the
/// mutex. Once a mutex is poisoned, all other threads are unable to access the
/// data by default as it is likely tainted (some invariant is not being
/// upheld).
/// mutex becomes poisoned if it recognizes that the thread holding it has
/// panicked.
///
/// For a mutex, this means that the [`lock`] and [`try_lock`] methods return a
/// Once a mutex is poisoned, all other threads are unable to access the data by
/// default as it is likely tainted (some invariant is not being upheld). For a
/// mutex, this means that the [`lock`] and [`try_lock`] methods return a
/// [`Result`] which indicates whether a mutex has been poisoned or not. Most
/// usage of a mutex will simply [`unwrap()`] these results, propagating panics
/// among threads to ensure that a possibly invalid invariant is not witnessed.
///
/// A poisoned mutex, however, does not prevent all access to the underlying
/// data. The [`PoisonError`] type has an [`into_inner`] method which will return
/// the guard that would have otherwise been returned on a successful lock. This
/// allows access to the data, despite the lock being poisoned.
/// Poisoning is only advisory: the [`PoisonError`] type has an [`into_inner`]
/// method which will return the guard that would have otherwise been returned
/// on a successful lock. This allows access to the data, despite the lock being
/// poisoned.
///
/// In addition, the panic detection is not ideal, so even unpoisoned mutexes
/// need to be handled with care, since certain panics may have been skipped.
/// Here is a non-exhaustive list of situations where this might occur:
///
/// - If a mutex is locked while a panic is underway, e.g. within a [`Drop`]
/// implementation or a [panic hook], panicking for the second time while the
/// lock is held will leave the mutex unpoisoned. Note that while double panic
/// usually aborts the program, [`catch_unwind`] can prevent this.
///
/// - Locking and unlocking the mutex across different panic contexts, e.g. by
/// storing the guard to a [`Cell`] within [`Drop::drop`] and accessing it
/// outside, or vice versa, can affect poisoning status in an unexpected way.
///
/// - Foreign exceptions do not currently trigger poisoning even in absence of
/// other panics.
///
/// While this rarely happens in realistic code, `unsafe` code cannot rely on
/// poisoning for soundness, since the behavior of poisoning can depend on
/// outside context. Here's an example of **incorrect** use of poisoning:
///
/// ```rust
/// use std::sync::Mutex;
///
/// struct MutexBox<T> {
/// data: Mutex<*mut T>,
/// }
///
/// impl<T> MutexBox<T> {
/// pub fn new(value: T) -> Self {
/// Self {
/// data: Mutex::new(Box::into_raw(Box::new(value))),
/// }
/// }
///
/// pub fn replace_with(&self, f: impl FnOnce(T) -> T) {
/// let ptr = self.data.lock().expect("poisoned");
/// // While `f` is running, the data is moved out of `*ptr`. If `f`
/// // panics, `*ptr` keeps pointing at a dropped value. The intention
/// // is that this will poison the mutex, so the following calls to
/// // `replace_with` will panic without reading `*ptr`. But since
/// // poisoning is not guaranteed to occur if this is run from a panic
/// // hook, this can lead to use-after-free.
/// unsafe {
/// (*ptr).write(f((*ptr).read()));
/// }
/// }
/// }
/// ```
///
/// [`new`]: Self::new
/// [`lock`]: Self::lock
@ -39,6 +88,9 @@ use crate::sys::sync as sys;
/// [`unwrap()`]: Result::unwrap
/// [`PoisonError`]: super::PoisonError
/// [`into_inner`]: super::PoisonError::into_inner
/// [panic hook]: crate::panic::set_hook
/// [`catch_unwind`]: crate::panic::catch_unwind
/// [`Cell`]: crate::cell::Cell
///
/// # Examples
///

View file

@ -136,7 +136,8 @@ impl Once {
/// it will *poison* this [`Once`] instance, causing all future invocations of
/// `call_once` to also panic.
///
/// This is similar to [poisoning with mutexes][poison].
/// This is similar to [poisoning with mutexes][poison], but this mechanism
/// is guaranteed to never skip panics within `f`.
///
/// [poison]: struct.Mutex.html#poisoning
#[inline]
@ -293,6 +294,9 @@ impl Once {
/// Blocks the current thread until initialization has completed, ignoring
/// poisoning.
///
/// If this [`Once`] has been poisoned, this function blocks until it
/// becomes completed, unlike [`Once::wait()`], which panics in this case.
#[stable(feature = "once_wait", since = "1.86.0")]
pub fn wait_force(&self) {
if !self.inner.is_completed() {

View file

@ -46,10 +46,12 @@ use crate::sys::sync as sys;
///
/// # Poisoning
///
/// An `RwLock`, like [`Mutex`], will become poisoned on a panic. Note, however,
/// that an `RwLock` may only be poisoned if a panic occurs while it is locked
/// exclusively (write mode). If a panic occurs in any reader, then the lock
/// will not be poisoned.
/// An `RwLock`, like [`Mutex`], will [usually] become poisoned on a panic. Note,
/// however, that an `RwLock` may only be poisoned if a panic occurs while it is
/// locked exclusively (write mode). If a panic occurs in any reader, then the
/// lock will not be poisoned.
///
/// [usually]: super::Mutex#poisoning
///
/// # Examples
///

View file

@ -749,6 +749,12 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
SourceType::InTree,
&[],
);
// Used for `compiletest` self-tests to have the path to the *staged* compiler. Getting this
// right is important, as `compiletest` is intended to only support one target spec JSON
// format, namely that of the staged compiler.
cargo.env("TEST_RUSTC", builder.rustc(compiler));
cargo.allow_features(COMPILETEST_ALLOW_FEATURES);
run_cargo_test(cargo, &[], &[], "compiletest self test", host, builder);
}

View file

@ -11,10 +11,11 @@ jobs:
if: github.repository == 'rust-lang/rustc-dev-guide'
uses: rust-lang/josh-sync/.github/workflows/rustc-pull.yml@main
with:
github-app-id: ${{ vars.APP_CLIENT_ID }}
zulip-stream-id: 196385
zulip-bot-email: "rustc-dev-guide-gha-notif-bot@rust-lang.zulipchat.com"
pr-base-branch: master
branch-name: rustc-pull
secrets:
zulip-api-token: ${{ secrets.ZULIP_API_TOKEN }}
token: ${{ secrets.GITHUB_TOKEN }}
github-app-secret: ${{ secrets.APP_PRIVATE_KEY }}

View file

@ -1 +1 @@
2b5e239c6b86cde974b0ef0f8e23754fb08ff3c5
32e7a4b92b109c24e9822c862a7c74436b50e564

View file

@ -176,6 +176,7 @@
- [Next-gen trait solving](./solve/trait-solving.md)
- [Invariants of the type system](./solve/invariants.md)
- [The solver](./solve/the-solver.md)
- [Candidate preference](./solve/candidate-preference.md)
- [Canonicalization](./solve/canonicalization.md)
- [Coinduction](./solve/coinduction.md)
- [Caching](./solve/caching.md)

View file

@ -0,0 +1,427 @@
# Candidate preference
There are multiple ways to prove `Trait` and `NormalizesTo` goals. Each such option is called a [`Candidate`]. If there are multiple applicable candidates, we prefer some candidates over others. We store the relevant information in their [`CandidateSource`].
This preference may result in incorrect inference or region constraints and would therefore be unsound during coherence. Because of this, we simply try to merge all candidates in coherence.
## `Trait` goals
Trait goals merge their applicable candidates in [`fn merge_trait_candidates`]. This document provides additional details and references to explain *why* we've got the current preference rules.
### `CandidateSource::BuiltinImpl(BuiltinImplSource::Trivial))`
Trivial builtin impls are builtin impls which are known to be always applicable for well-formed types. This means that if one exists, using another candidate should never have fewer constraints. We currently only consider `Sized` - and `MetaSized` - impls to be trivial.
This is necessary to prevent a lifetime error for the following pattern
```rust
trait Trait<T>: Sized {}
impl<'a> Trait<u32> for &'a str {}
impl<'a> Trait<i32> for &'a str {}
fn is_sized<T: Sized>(_: T) {}
fn foo<'a, 'b, T>(x: &'b str)
where
&'a str: Trait<T>,
{
// Elaborating the `&'a str: Trait<T>` where-bound results in a
// `&'a str: Sized` where-bound. We do not want to prefer this
// over the builtin impl.
is_sized(x);
}
```
This preference is incorrect in case the builtin impl has a nested goal which relies on a non-param where-clause
```rust
struct MyType<'a, T: ?Sized>(&'a (), T);
fn is_sized<T>() {}
fn foo<'a, T: ?Sized>()
where
(MyType<'a, T>,): Sized,
MyType<'static, T>: Sized,
{
// The where-bound is trivial while the builtin `Sized` impl for tuples
// requires proving `MyType<'a, T>: Sized` which can only be proven by
// using the where-clause, adding an unnecessary `'static` constraint.
is_sized::<(MyType<'a, T>,)>();
//~^ ERROR lifetime may not live long enough
}
```
### `CandidateSource::ParamEnv`
Once there's at least one *non-global* `ParamEnv` candidate, we prefer *all* `ParamEnv` candidates over other candidate kinds.
A where-bound is global if it is not higher-ranked and doesn't contain any generic parameters. It may contain `'static`.
We try to apply where-bounds over other candidates as users tends to have the most control over them, so they can most easily
adjust them in case our candidate preference is incorrect.
#### Preference over `Impl` candidates
This is necessary to avoid region errors in the following example
```rust
trait Trait<'a> {}
impl<T> Trait<'static> for T {}
fn impls_trait<'a, T: Trait<'a>>() {}
fn foo<'a, T: Trait<'a>>() {
impls_trait::<'a, T>();
}
```
We also need this as shadowed impls can result in currently ambiguous solver cycles: [trait-system-refactor-initiative#76]. Without preference we'd be forced to fail with ambiguity
errors if the where-bound results in region constraints to avoid incompleteness.
```rust
trait Super {
type SuperAssoc;
}
trait Trait: Super<SuperAssoc = Self::TraitAssoc> {
type TraitAssoc;
}
impl<T, U> Trait for T
where
T: Super<SuperAssoc = U>,
{
type TraitAssoc = U;
}
fn overflow<T: Trait>() {
// We can use the elaborated `Super<SuperAssoc = Self::TraitAssoc>` where-bound
// to prove the where-bound of the `T: Trait` implementation. This currently results in
// overflow.
let x: <T as Trait>::TraitAssoc;
}
```
This preference causes a lot of issues. See [#24066]. Most of the
issues are caused by prefering where-bounds over impls even if the where-bound guides type inference:
```rust
trait Trait<T> {
fn call_me(&self, x: T) {}
}
impl<T> Trait<u32> for T {}
impl<T> Trait<i32> for T {}
fn bug<T: Trait<U>, U>(x: T) {
x.call_me(1u32);
//~^ ERROR mismatched types
}
```
However, even if we only apply this preference if the where-bound doesn't guide inference, it may still result
in incorrect lifetime constraints:
```rust
trait Trait<'a> {}
impl<'a> Trait<'a> for &'a str {}
fn impls_trait<'a, T: Trait<'a>>(_: T) {}
fn foo<'a, 'b>(x: &'b str)
where
&'a str: Trait<'b>
{
// Need to prove `&'x str: Trait<'b>` with `'b: 'x`.
impls_trait::<'b, _>(x);
//~^ ERROR lifetime may not live long enough
}
```
#### Preference over `AliasBound` candidates
This is necessary to avoid region errors in the following example
```rust
trait Bound<'a> {}
trait Trait<'a> {
type Assoc: Bound<'a>;
}
fn impls_bound<'b, T: Bound<'b>>() {}
fn foo<'a, 'b, 'c, T>()
where
T: Trait<'a>,
for<'hr> T::Assoc: Bound<'hr>,
{
impls_bound::<'b, T::Assoc>();
impls_bound::<'c, T::Assoc>();
}
```
It can also result in unnecessary constraints
```rust
trait Bound<'a> {}
trait Trait<'a> {
type Assoc: Bound<'a>;
}
fn impls_bound<'b, T: Bound<'b>>() {}
fn foo<'a, 'b, T>()
where
T: for<'hr> Trait<'hr>,
<T as Trait<'b>>::Assoc: Bound<'a>,
{
// Using the where-bound for `<T as Trait<'a>>::Assoc: Bound<'a>`
// unnecessarily equates `<T as Trait<'a>>::Assoc` with the
// `<T as Trait<'b>>::Assoc` from the env.
impls_bound::<'a, <T as Trait<'a>>::Assoc>();
// For a `<T as Trait<'b>>::Assoc: Bound<'b>` the self type of the
// where-bound matches, but the arguments of the trait bound don't.
impls_bound::<'b, <T as Trait<'b>>::Assoc>();
}
```
#### Why no preference for global where-bounds
Global where-bounds are either fully implied by an impl or unsatisfiable. If they are unsatisfiable, we don't really care what happens. If a where-bound is fully implied then using the impl to prove the trait goal cannot result in additional constraints. For trait goals this is only useful for where-bounds which use `'static`:
```rust
trait A {
fn test(&self);
}
fn foo(x: &dyn A)
where
dyn A + 'static: A, // Using this bound would lead to a lifetime error.
{
x.test();
}
```
More importantly, by using impls here we prevent global where-bounds from shadowing impls when normalizing associated types. There are no known issues from preferring impls over global where-bounds.
#### Why still consider global where-bounds
Given that we just use impls even if there exists a global where-bounds, you may ask why we don't just ignore these global where-bounds entirely: we use them to weaken the inference guidance from non-global where-bounds.
Without a global where-bound, we currently prefer non-global where bounds even though there would be an applicable impl as well. By adding a non-global where-bound, this unnecessary inference guidance is disabled, allowing the following to compile:
```rust
fn check<Color>(color: Color)
where
Vec: Into<Color> + Into<f32>,
{
let _: f32 = Vec.into();
// Without the global `Vec: Into<f32>` bound we'd
// eagerly use the non-global `Vec: Into<Color>` bound
// here, causing this to fail.
}
struct Vec;
impl From<Vec> for f32 {
fn from(_: Vec) -> Self {
loop {}
}
}
```
### `CandidateSource::AliasBound`
We prefer alias-bound candidates over impls. We currently use this preference to guide type inference, causing the following to compile. I personally don't think this preference is desirable 🤷
```rust
pub trait Dyn {
type Word: Into<u64>;
fn d_tag(&self) -> Self::Word;
fn tag32(&self) -> Option<u32> {
self.d_tag().into().try_into().ok()
// prove `Self::Word: Into<?0>` and then select a method
// on `?0`, needs eager inference.
}
}
```
```rust
fn impl_trait() -> impl Into<u32> {
0u16
}
fn main() {
// There are two possible types for `x`:
// - `u32` by using the "alias bound" of `impl Into<u32>`
// - `impl Into<u32>`, i.e. `u16`, by using `impl<T> From<T> for T`
//
// We infer the type of `x` to be `u32` even though this is not
// strictly necessary and can even lead to surprising errors.
let x = impl_trait().into();
println!("{}", std::mem::size_of_val(&x));
}
```
This preference also avoids ambiguity due to region constraints, I don't know whether people rely on this in practice.
```rust
trait Bound<'a> {}
impl<T> Bound<'static> for T {}
trait Trait<'a> {
type Assoc: Bound<'a>;
}
fn impls_bound<'b, T: Bound<'b>>() {}
fn foo<'a, T: Trait<'a>>() {
// Should we infer this to `'a` or `'static`.
impls_bound::<'_, T::Assoc>();
}
```
### `CandidateSource::BuiltinImpl(BuiltinImplSource::Object(_))`
We prefer builtin trait object impls over user-written impls. This is **unsound** and should be remoed in the future. See [#57893](https://github.com/rust-lang/rust/issues/57893) and [#141347](https://github.com/rust-lang/rust/pull/141347) for more details.
## `NormalizesTo` goals
The candidate preference behavior during normalization is implemented in [`fn assemble_and_merge_candidates`].
### Where-bounds shadow impls
Normalization of associated items does not consider impls if the corresponding trait goal has been proven via a `ParamEnv` or `AliasBound` candidate.
This means that for where-bounds which do not constrain associated types, the associated types remain *rigid*.
This is necessary to avoid unnecessary region constraints from applying impls.
```rust
trait Trait<'a> {
type Assoc;
}
impl Trait<'static> for u32 {
type Assoc = u32;
}
fn bar<'b, T: Trait<'b>>() -> T::Assoc { todo!() }
fn foo<'a>()
where
u32: Trait<'a>,
{
// Normalizing the return type would use the impl, proving
// the `T: Trait` where-bound would use the where-bound, resulting
// in different region constraints.
bar::<'_, u32>();
}
```
### We always consider `AliasBound` candidates
In case the where-bound does not specify the associated item, we consider `AliasBound` candidates instead of treating the alias as rigid, even though the trait goal was proven via a `ParamEnv` candidate.
```rust
trait Super {
type Assoc;
}
trait Bound {
type Assoc: Super<Assoc = u32>;
}
trait Trait: Super {}
// Elaborating the environment results in a `T::Assoc: Super` where-bound.
// This where-bound must not prevent normalization via the `Super<Assoc = u32>`
// item bound.
fn heck<T: Bound<Assoc: Trait>>(x: <T::Assoc as Super>::Assoc) -> u32 {
x
}
```
Using such an alias can result in additional region constraints, cc [#133044].
```rust
trait Bound<'a> {
type Assoc;
}
trait Trait {
type Assoc: Bound<'static, Assoc = u32>;
}
fn heck<'a, T: Trait<Assoc: Bound<'a>>>(x: <T::Assoc as Bound<'a>>::Assoc) {
// Normalizing the associated type requires `T::Assoc: Bound<'static>` as it
// uses the `Bound<'static>` alias-bound instead of keeping the alias rigid.
drop(x);
}
```
### We prefer `ParamEnv` candidates over `AliasBound`
While we use `AliasBound` candidates if the where-bound does not specify the associated type, in case it does, we prefer the where-bound.
This is necessary for the following example:
```rust
// Make sure we prefer the `I::IntoIterator: Iterator<Item = ()>`
// where-bound over the `I::Intoiterator: Iterator<Item = I::Item>`
// alias-bound.
trait Iterator {
type Item;
}
trait IntoIterator {
type Item;
type IntoIter: Iterator<Item = Self::Item>;
}
fn normalize<I: Iterator<Item = ()>>() {}
fn foo<I>()
where
I: IntoIterator,
I::IntoIter: Iterator<Item = ()>,
{
// We need to prefer the `I::IntoIterator: Iterator<Item = ()>`
// where-bound over the `I::Intoiterator: Iterator<Item = I::Item>`
// alias-bound.
normalize::<I::IntoIter>();
}
```
### We always consider where-bounds
Even if the trait goal was proven via an impl, we still prefer `ParamEnv` candidates, if any exist.
#### We prefer "orphaned" where-bounds
We add "orphaned" `Projection` clauses into the `ParamEnv` when normalizing item bounds of GATs and RPITIT in `fn check_type_bounds`.
We need to prefer these `ParamEnv` candidates over impls and other where-bounds.
```rust
#![feature(associated_type_defaults)]
trait Foo {
// We should be able to prove that `i32: Baz<Self>` because of
// the impl below, which requires that `Self::Bar<()>: Eq<i32>`
// which is true, because we assume `for<T> Self::Bar<T> = i32`.
type Bar<T>: Baz<Self> = i32;
}
trait Baz<T: ?Sized> {}
impl<T: Foo + ?Sized> Baz<T> for i32 where T::Bar<()>: Eq<i32> {}
trait Eq<T> {}
impl<T> Eq<T> for T {}
```
I don't fully understand the cases where this preference is actually necessary and haven't been able to exploit this in fun ways yet, but 🤷
#### We prefer global where-bounds over impls
This is necessary for the following to compile. I don't know whether anything relies on it in practice 🤷
```rust
trait Id {
type This;
}
impl<T> Id for T {
type This = T;
}
fn foo<T>(x: T) -> <u32 as Id>::This
where
u32: Id<This = T>,
{
x
}
```
This means normalization can result in additional region constraints, cc [#133044].
```rust
trait Trait {
type Assoc;
}
impl Trait for &u32 {
type Assoc = u32;
}
fn trait_bound<T: Trait>() {}
fn normalize<T: Trait<Assoc = u32>>() {}
fn foo<'a>()
where
&'static u32: Trait<Assoc = u32>,
{
trait_bound::<&'a u32>(); // ok, proven via impl
normalize::<&'a u32>(); // error, proven via where-bound
}
```
[`Candidate`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/assembly/struct.Candidate.html
[`CandidateSource`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_next_trait_solver/solve/enum.CandidateSource.html
[`fn merge_trait_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs#L1342-L1424
[`fn assemble_and_merge_candidates`]: https://github.com/rust-lang/rust/blob/e3ee7f7aea5b45af3b42b5e4713da43876a65ac9/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs#L920-L1003
[trait-system-refactor-initiative#76]: https://github.com/rust-lang/trait-system-refactor-initiative/issues/76
[#24066]: https://github.com/rust-lang/rust/issues/24066
[#133044]: https://github.com/rust-lang/rust/issues/133044

View file

@ -52,6 +52,8 @@ not be exhaustive. Directives can generally be found by browsing the
### Auxiliary builds
See [Building auxiliary crates](compiletest.html#building-auxiliary-crates)
| Directive | Explanation | Supported test suites | Possible values |
|-----------------------|-------------------------------------------------------------------------------------------------------|-----------------------|-----------------------------------------------|
| `aux-bin` | Build a aux binary, made available in `auxiliary/bin` relative to test directory | All except `run-make` | Path to auxiliary `.rs` file |
@ -61,8 +63,7 @@ not be exhaustive. Directives can generally be found by browsing the
| `proc-macro` | Similar to `aux-build`, but for aux forces host and don't use `-Cprefer-dynamic`[^pm]. | All except `run-make` | Path to auxiliary proc-macro `.rs` file |
| `build-aux-docs` | Build docs for auxiliaries as well. Note that this only works with `aux-build`, not `aux-crate`. | All except `run-make` | N/A |
[^pm]: please see the Auxiliary proc-macro section in the
[compiletest](./compiletest.md) chapter for specifics.
[^pm]: please see the [Auxiliary proc-macro section](compiletest.html#auxiliary-proc-macro) in the compiletest chapter for specifics.
### Controlling outcome expectations

View file

@ -6,12 +6,12 @@ need to install Docker on a Linux, Windows, or macOS system (typically Linux
will be much faster than Windows or macOS because the latter use virtual
machines to emulate a Linux environment).
Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. If you want to run a CI job locally in the simplest way possible, you can use a provided helper Python script that tries to replicate what happens on CI as closely as possible:
Jobs running in CI are configured through a set of bash scripts, and it is not always trivial to reproduce their behavior locally. If you want to run a CI job locally in the simplest way possible, you can use a provided helper `citool` that tries to replicate what happens on CI as closely as possible:
```bash
python3 src/ci/github-actions/ci.py run-local <job-name>
cargo run --manifest-path src/ci/citool/Cargo.toml run-local <job-name>
# For example:
python3 src/ci/github-actions/ci.py run-local dist-x86_64-linux-alt
cargo run --manifest-path src/ci/citool/Cargo.toml run-local dist-x86_64-linux-alt
```
If the above script does not work for you, you would like to have more control of the Docker image execution, or you want to understand what exactly happens during Docker job execution, then continue reading below.
@ -53,15 +53,6 @@ Some additional notes about using the interactive mode:
containers. With the container name, run `docker exec -it <CONTAINER>
/bin/bash` where `<CONTAINER>` is the container name like `4ba195e95cef`.
The approach described above is a relatively low-level interface for running the Docker images
directly. If you want to run a full CI Linux job locally with Docker, in a way that is as close to CI as possible, you can use the following command:
```bash
cargo run --manifest-path src/ci/citool/Cargo.toml run-local <job-name>
# For example:
cargo run --manifest-path src/ci/citool/Cargo.toml run-local dist-x86_64-linux-alt
```
[Docker]: https://www.docker.com/
[`src/ci/docker`]: https://github.com/rust-lang/rust/tree/master/src/ci/docker
[`src/ci/docker/run.sh`]: https://github.com/rust-lang/rust/blob/master/src/ci/docker/run.sh

View file

@ -62,9 +62,6 @@ allow-unauthenticated = [
# Documentation at: https://forge.rust-lang.org/triagebot/issue-links.html
[issue-links]
# Automatically close and reopen PRs made by bots to run CI on them
[bot-pull-requests]
[behind-upstream]
days-threshold = 7

View file

@ -486,6 +486,7 @@ impl fmt::Display for Display<'_> {
(sym::debug_assertions, None) => "debug-assertions enabled",
(sym::target_os, Some(os)) => match os.as_str() {
"android" => "Android",
"cygwin" => "Cygwin",
"dragonfly" => "DragonFly BSD",
"emscripten" => "Emscripten",
"freebsd" => "FreeBSD",

View file

@ -763,13 +763,13 @@ impl Item {
.iter()
.filter_map(|attr| match attr {
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
Some(format!("#[link_section = \"{name}\"]"))
Some(format!("#[unsafe(link_section = \"{name}\")]"))
}
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
Some("#[no_mangle]".to_string())
Some("#[unsafe(no_mangle)]".to_string())
}
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
Some(format!("#[export_name = \"{name}\"]"))
Some(format!("#[unsafe(export_name = \"{name}\")]"))
}
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
Some("#[non_exhaustive]".to_string())

View file

@ -941,13 +941,21 @@ fn preprocess_link(
ori_link: &MarkdownLink,
dox: &str,
) -> Option<Result<PreprocessingInfo, PreprocessingError>> {
// certain link kinds cannot have their path be urls,
// so they should not be ignored, no matter how much they look like urls.
// e.g. [https://example.com/] is not a link to example.com.
let can_be_url = !matches!(
ori_link.kind,
LinkType::ShortcutUnknown | LinkType::CollapsedUnknown | LinkType::ReferenceUnknown
);
// [] is mostly likely not supposed to be a link
if ori_link.link.is_empty() {
return None;
}
// Bail early for real links.
if ori_link.link.contains('/') {
if can_be_url && ori_link.link.contains('/') {
return None;
}
@ -972,7 +980,7 @@ fn preprocess_link(
Ok(None) => (None, link, link),
Err((err_msg, relative_range)) => {
// Only report error if we would not have ignored this link. See issue #83859.
if !should_ignore_link_with_disambiguators(link) {
if !(can_be_url && should_ignore_link_with_disambiguators(link)) {
let disambiguator_range = match range_between_backticks(&ori_link.range, dox) {
MarkdownLinkRange::Destination(no_backticks_range) => {
MarkdownLinkRange::Destination(
@ -989,7 +997,25 @@ fn preprocess_link(
}
};
if should_ignore_link(path_str) {
// If there's no backticks, be lenient and revert to the old behavior.
// This is to prevent churn by linting on stuff that isn't meant to be a link.
// only shortcut links have simple enough syntax that they
// are likely to be written accidentally, collapsed and reference links
// need 4 metachars, and reference links will not usually use
// backticks in the reference name.
// therefore, only shortcut syntax gets the lenient behavior.
//
// here's a truth table for how link kinds that cannot be urls are handled:
//
// |-------------------------------------------------------|
// | | is shortcut link | not shortcut link |
// |--------------|--------------------|-------------------|
// | has backtick | never ignore | never ignore |
// | no backtick | ignore if url-like | never ignore |
// |-------------------------------------------------------|
let ignore_urllike =
can_be_url || (ori_link.kind == LinkType::ShortcutUnknown && !ori_link.link.contains('`'));
if ignore_urllike && should_ignore_link(path_str) {
return None;
}

View file

@ -203,22 +203,8 @@ impl ConfigBuilder {
}
args.push("--rustc-path".to_string());
// This is a subtle/fragile thing. On rust-lang CI, there is no global
// `rustc`, and Cargo doesn't offer a convenient way to get the path to
// `rustc`. Fortunately bootstrap sets `RUSTC` for us, which is pointing
// to the stage0 compiler.
//
// Otherwise, if you are running compiletests's tests manually, you
// probably don't have `RUSTC` set, in which case this falls back to the
// global rustc. If your global rustc is too far out of sync with stage0,
// then this may cause confusing errors. Or if for some reason you don't
// have rustc in PATH, that would also fail.
args.push(std::env::var("RUSTC").unwrap_or_else(|_| {
eprintln!(
"warning: RUSTC not set, using global rustc (are you not running via bootstrap?)"
);
"rustc".to_string()
}));
args.push(std::env::var("TEST_RUSTC").expect("must be configured by bootstrap"));
crate::parse_config(args)
}
}

View file

@ -765,12 +765,6 @@ impl<'test> TestCx<'test> {
}
if !unexpected.is_empty() || !not_found.is_empty() {
self.error(&format!(
"{} unexpected diagnostics reported, {} expected diagnostics not reported",
unexpected.len(),
not_found.len()
));
// Emit locations in a format that is short (relative paths) but "clickable" in editors.
// Also normalize path separators to `/`.
let file_name = self
@ -794,19 +788,20 @@ impl<'test> TestCx<'test> {
|suggestions: &mut Vec<_>, e: &Error, kind, line, msg, color, rank| {
let mut ret = String::new();
if kind {
ret += &format!("{} {}", "with kind".color(color), e.kind);
ret += &format!("{} {}", "with different kind:".color(color), e.kind);
}
if line {
if !ret.is_empty() {
ret.push(' ');
}
ret += &format!("{} {}", "on line".color(color), line_str(e));
ret += &format!("{} {}", "on different line:".color(color), line_str(e));
}
if msg {
if !ret.is_empty() {
ret.push(' ');
}
ret += &format!("{} {}", "with message".color(color), e.msg.cyan());
ret +=
&format!("{} {}", "with different message:".color(color), e.msg.cyan());
}
suggestions.push((ret, rank));
};
@ -829,17 +824,20 @@ impl<'test> TestCx<'test> {
// - only known line - meh, but suggested
// - others are not worth suggesting
if !unexpected.is_empty() {
let header = "--- reported in JSON output but not expected in test file ---";
println!("{}", header.green());
self.error(&format!(
"{} diagnostics reported in JSON output but not expected in test file",
unexpected.len(),
));
for error in &unexpected {
print_error(error);
let mut suggestions = Vec::new();
for candidate in &not_found {
let kind_mismatch = candidate.kind != error.kind;
let mut push_red_suggestion = |line, msg, rank| {
push_suggestion(
&mut suggestions,
candidate,
candidate.kind != error.kind,
kind_mismatch,
line,
msg,
Color::Red,
@ -851,26 +849,28 @@ impl<'test> TestCx<'test> {
} else if candidate.line_num.is_some()
&& candidate.line_num == error.line_num
{
push_red_suggestion(false, true, 1);
push_red_suggestion(false, true, if kind_mismatch { 2 } else { 1 });
}
}
show_suggestions(suggestions, "expected", Color::Red);
}
println!("{}", "---".green());
}
if !not_found.is_empty() {
let header = "--- expected in test file but not reported in JSON output ---";
println!("{}", header.red());
self.error(&format!(
"{} diagnostics expected in test file but not reported in JSON output",
not_found.len()
));
for error in &not_found {
print_error(error);
let mut suggestions = Vec::new();
for candidate in unexpected.iter().chain(&unimportant) {
let kind_mismatch = candidate.kind != error.kind;
let mut push_green_suggestion = |line, msg, rank| {
push_suggestion(
&mut suggestions,
candidate,
candidate.kind != error.kind,
kind_mismatch,
line,
msg,
Color::Green,
@ -882,13 +882,12 @@ impl<'test> TestCx<'test> {
} else if candidate.line_num.is_some()
&& candidate.line_num == error.line_num
{
push_green_suggestion(false, true, 1);
push_green_suggestion(false, true, if kind_mismatch { 2 } else { 1 });
}
}
show_suggestions(suggestions, "reported", Color::Green);
}
println!("{}", "---".red());
}
panic!(
"errors differ from expected\nstatus: {}\ncommand: {}\n",

View file

@ -1,17 +1,24 @@
// Test LVI load hardening on SGX enclave code
// Test LVI load hardening on SGX enclave code, specifically that `ret` is rewritten.
//@ add-core-stubs
//@ assembly-output: emit-asm
//@ compile-flags: --crate-type staticlib
//@ only-x86_64-fortanix-unknown-sgx
//@ compile-flags: --target x86_64-fortanix-unknown-sgx -Copt-level=0
//@ needs-llvm-components: x86
#![feature(no_core, lang_items, f16)]
#![crate_type = "lib"]
#![no_core]
extern crate minicore;
use minicore::*;
#[no_mangle]
pub extern "C" fn plus_one(r: &mut u64) {
*r = *r + 1;
pub extern "C" fn dereference(a: &mut u64) -> u64 {
// CHECK-LABEL: dereference
// CHECK: lfence
// CHECK: mov
// CHECK: popq [[REGISTER:%[a-z]+]]
// CHECK-NEXT: lfence
// CHECK-NEXT: jmpq *[[REGISTER]]
*a
}
// CHECK: plus_one
// CHECK: lfence
// CHECK-NEXT: incq
// CHECK: popq [[REGISTER:%[a-z]+]]
// CHECK-NEXT: lfence
// CHECK-NEXT: jmpq *[[REGISTER]]

View file

@ -1,12 +1,20 @@
// Test LVI ret hardening on generic rust code
//@ add-core-stubs
//@ assembly-output: emit-asm
//@ compile-flags: --crate-type staticlib
//@ only-x86_64-fortanix-unknown-sgx
//@ compile-flags: --target x86_64-fortanix-unknown-sgx
//@ needs-llvm-components: x86
#![feature(no_core, lang_items, f16)]
#![crate_type = "lib"]
#![no_core]
extern crate minicore;
use minicore::*;
#[no_mangle]
pub extern "C" fn myret() {}
// CHECK: myret:
// CHECK-LABEL: myret:
// CHECK: popq [[REGISTER:%[a-z]+]]
// CHECK-NEXT: lfence
// CHECK-NEXT: jmpq *[[REGISTER]]

View file

@ -1,13 +1,22 @@
// Test LVI load hardening on SGX inline assembly code
//@ add-core-stubs
//@ assembly-output: emit-asm
//@ compile-flags: --crate-type staticlib
//@ only-x86_64-fortanix-unknown-sgx
//@ compile-flags: --target x86_64-fortanix-unknown-sgx
//@ needs-llvm-components: x86
use std::arch::asm;
#![feature(no_core, lang_items, f16)]
#![crate_type = "lib"]
#![no_core]
extern crate minicore;
use minicore::*;
#[no_mangle]
pub extern "C" fn get(ptr: *const u64) -> u64 {
// CHECK-LABEL: get
// CHECK: movq
// CHECK-NEXT: lfence
let value: u64;
unsafe {
asm!("mov {}, [{}]",
@ -17,18 +26,13 @@ pub extern "C" fn get(ptr: *const u64) -> u64 {
value
}
// CHECK: get
// CHECK: movq
// CHECK-NEXT: lfence
#[no_mangle]
pub extern "C" fn myret() {
// CHECK-LABEL: myret
// CHECK: shlq $0, (%rsp)
// CHECK-NEXT: lfence
// CHECK-NEXT: retq
unsafe {
asm!("ret");
}
}
// CHECK: myret
// CHECK: shlq $0, (%rsp)
// CHECK-NEXT: lfence
// CHECK-NEXT: retq

View file

@ -0,0 +1,15 @@
- // MIR for `cannot_opt_generic` before RemoveUnneededDrops
+ // MIR for `cannot_opt_generic` after RemoveUnneededDrops
fn cannot_opt_generic(_1: T) -> () {
let mut _0: ();
bb0: {
drop(_1) -> [return: bb1, unwind unreachable];
}
bb1: {
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `cannot_opt_generic` before RemoveUnneededDrops
+ // MIR for `cannot_opt_generic` after RemoveUnneededDrops
fn cannot_opt_generic(_1: T) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: T;
scope 1 (inlined std::mem::drop::<T>) {
}
bb0: {
nop;
StorageLive(_3);
_3 = move _1;
drop(_3) -> [return: bb1, unwind unreachable];
}
bb1: {
StorageDead(_3);
nop;
nop;
return;
}
}

View file

@ -1,30 +0,0 @@
- // MIR for `cannot_opt_generic` before RemoveUnneededDrops
+ // MIR for `cannot_opt_generic` after RemoveUnneededDrops
fn cannot_opt_generic(_1: T) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: T;
scope 1 (inlined std::mem::drop::<T>) {
}
bb0: {
nop;
StorageLive(_3);
_3 = move _1;
drop(_3) -> [return: bb2, unwind: bb1];
}
bb1 (cleanup): {
resume;
}
bb2: {
StorageDead(_3);
nop;
nop;
return;
}
}

View file

@ -0,0 +1,15 @@
- // MIR for `dont_opt` before RemoveUnneededDrops
+ // MIR for `dont_opt` after RemoveUnneededDrops
fn dont_opt(_1: Vec<bool>) -> () {
let mut _0: ();
bb0: {
drop(_1) -> [return: bb1, unwind unreachable];
}
bb1: {
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `dont_opt` before RemoveUnneededDrops
+ // MIR for `dont_opt` after RemoveUnneededDrops
fn dont_opt(_1: Vec<bool>) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: std::vec::Vec<bool>;
scope 1 (inlined std::mem::drop::<Vec<bool>>) {
}
bb0: {
nop;
StorageLive(_3);
_3 = move _1;
drop(_3) -> [return: bb1, unwind unreachable];
}
bb1: {
StorageDead(_3);
nop;
nop;
return;
}
}

View file

@ -1,30 +0,0 @@
- // MIR for `dont_opt` before RemoveUnneededDrops
+ // MIR for `dont_opt` after RemoveUnneededDrops
fn dont_opt(_1: Vec<bool>) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: std::vec::Vec<bool>;
scope 1 (inlined std::mem::drop::<Vec<bool>>) {
}
bb0: {
nop;
StorageLive(_3);
_3 = move _1;
drop(_3) -> [return: bb2, unwind: bb1];
}
bb1 (cleanup): {
resume;
}
bb2: {
StorageDead(_3);
nop;
nop;
return;
}
}

View file

@ -0,0 +1,15 @@
- // MIR for `opt` before RemoveUnneededDrops
+ // MIR for `opt` after RemoveUnneededDrops
fn opt(_1: bool) -> () {
let mut _0: ();
bb0: {
- drop(_1) -> [return: bb1, unwind unreachable];
- }
-
- bb1: {
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `opt` before RemoveUnneededDrops
+ // MIR for `opt` after RemoveUnneededDrops
fn opt(_1: bool) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: bool;
scope 1 (inlined std::mem::drop::<bool>) {
}
bb0: {
- nop;
StorageLive(_3);
_3 = copy _1;
- drop(_3) -> [return: bb1, unwind unreachable];
- }
-
- bb1: {
StorageDead(_3);
- nop;
- nop;
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `opt` before RemoveUnneededDrops
+ // MIR for `opt` after RemoveUnneededDrops
fn opt(_1: bool) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: bool;
scope 1 (inlined std::mem::drop::<bool>) {
}
bb0: {
- nop;
StorageLive(_3);
_3 = copy _1;
- drop(_3) -> [return: bb1, unwind continue];
- }
-
- bb1: {
StorageDead(_3);
- nop;
- nop;
return;
}
}

View file

@ -0,0 +1,15 @@
- // MIR for `opt_generic_copy` before RemoveUnneededDrops
+ // MIR for `opt_generic_copy` after RemoveUnneededDrops
fn opt_generic_copy(_1: T) -> () {
let mut _0: ();
bb0: {
- drop(_1) -> [return: bb1, unwind unreachable];
- }
-
- bb1: {
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `opt_generic_copy` before RemoveUnneededDrops
+ // MIR for `opt_generic_copy` after RemoveUnneededDrops
fn opt_generic_copy(_1: T) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: T;
scope 1 (inlined std::mem::drop::<T>) {
}
bb0: {
- nop;
StorageLive(_3);
_3 = copy _1;
- drop(_3) -> [return: bb1, unwind unreachable];
- }
-
- bb1: {
StorageDead(_3);
- nop;
- nop;
return;
}
}

View file

@ -1,26 +0,0 @@
- // MIR for `opt_generic_copy` before RemoveUnneededDrops
+ // MIR for `opt_generic_copy` after RemoveUnneededDrops
fn opt_generic_copy(_1: T) -> () {
debug x => _1;
let mut _0: ();
let _2: ();
let mut _3: T;
scope 1 (inlined std::mem::drop::<T>) {
}
bb0: {
- nop;
StorageLive(_3);
_3 = copy _1;
- drop(_3) -> [return: bb1, unwind continue];
- }
-
- bb1: {
StorageDead(_3);
- nop;
- nop;
return;
}
}

View file

@ -1,28 +1,56 @@
// skip-filecheck
// EMIT_MIR_FOR_EACH_PANIC_STRATEGY
//@ test-mir-pass: RemoveUnneededDrops
#![feature(custom_mir, core_intrinsics)]
use std::intrinsics::mir::*;
// EMIT_MIR remove_unneeded_drops.opt.RemoveUnneededDrops.diff
#[custom_mir(dialect = "runtime")]
fn opt(x: bool) {
drop(x);
// CHECK-LABEL: fn opt(
// CHECK-NOT: drop(
mir! {
{ Drop(x, ReturnTo(bb1), UnwindUnreachable()) }
bb1 = { Return() }
}
}
// EMIT_MIR remove_unneeded_drops.dont_opt.RemoveUnneededDrops.diff
#[custom_mir(dialect = "runtime")]
fn dont_opt(x: Vec<bool>) {
drop(x);
// CHECK-LABEL: fn dont_opt(
// CHECK: drop(
mir! {
{ Drop(x, ReturnTo(bb1), UnwindUnreachable()) }
bb1 = { Return() }
}
}
// EMIT_MIR remove_unneeded_drops.opt_generic_copy.RemoveUnneededDrops.diff
#[custom_mir(dialect = "runtime")]
fn opt_generic_copy<T: Copy>(x: T) {
drop(x);
// CHECK-LABEL: fn opt_generic_copy(
// CHECK-NOT: drop(
mir! {
{ Drop(x, ReturnTo(bb1), UnwindUnreachable()) }
bb1 = { Return() }
}
}
// EMIT_MIR remove_unneeded_drops.cannot_opt_generic.RemoveUnneededDrops.diff
// since the pass is not running on monomorphisized code,
// we can't (but probably should) optimize this
#[custom_mir(dialect = "runtime")]
fn cannot_opt_generic<T>(x: T) {
drop(x);
// CHECK-LABEL: fn cannot_opt_generic(
// CHECK: drop(
mir! {
{ Drop(x, ReturnTo(bb1), UnwindUnreachable()) }
bb1 = { Return() }
}
}
fn main() {
// CHECK-LABEL: fn main(
opt(true);
opt_generic_copy(42);
cannot_opt_generic(42);

View file

@ -13,42 +13,56 @@
//@ only-x86_64-fortanix-unknown-sgx
use run_make_support::{cmd, cwd, llvm_filecheck, llvm_objdump, regex, set_current_dir, target};
use run_make_support::{
cargo, cwd, llvm_filecheck, llvm_objdump, regex, run, set_current_dir, target,
};
fn main() {
let main_dir = cwd();
set_current_dir("enclave");
// HACK(eddyb) sets `RUSTC_BOOTSTRAP=1` so Cargo can accept nightly features.
// These come from the top-level Rust workspace, that this crate is not a
// member of, but Cargo tries to load the workspace `Cargo.toml` anyway.
cmd("cargo")
.env("RUSTC_BOOTSTRAP", "1")
cargo()
.arg("-v")
.arg("run")
.arg("build")
.arg("--target")
.arg(target())
.current_dir("enclave")
.env("CC_x86_64_fortanix_unknown_sgx", "clang")
.env(
"CFLAGS_x86_64_fortanix_unknown_sgx",
"-D__ELF__ -isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening",
)
.env("CXX_x86_64_fortanix_unknown_sgx", "clang++")
.env(
"CXXFLAGS_x86_64_fortanix_unknown_sgx",
"-D__ELF__ -isystem/usr/include/x86_64-linux-gnu -mlvi-hardening -mllvm -x86-experimental-lvi-inline-asm-hardening",
)
.run();
set_current_dir(&main_dir);
// Rust has various ways of adding code to a binary:
// Rust has several ways of including machine code into a binary:
//
// - Rust code
// - Inline assembly
// - Global assembly
// - C/C++ code compiled as part of Rust crates
// For those different kinds, we do have very small code examples that should be
// mitigated in some way. Mostly we check that ret instructions should no longer be present.
//
// For each of those, check that the mitigations are applied. Mostly we check
// that ret instructions are no longer present.
// Check that normal rust code has the right mitigations.
check("unw_getcontext", "unw_getcontext.checks");
check("__libunwind_Registers_x86_64_jumpto", "jumpto.checks");
check("std::io::stdio::_print::[[:alnum:]]+", "print.with_frame_pointers.checks");
// Check that rust global assembly has the right mitigations.
check("rust_plus_one_global_asm", "rust_plus_one_global_asm.checks");
// Check that C code compiled using the `cc` crate has the right mitigations.
check("cc_plus_one_c", "cc_plus_one_c.checks");
check("cc_plus_one_c_asm", "cc_plus_one_c_asm.checks");
check("cc_plus_one_cxx", "cc_plus_one_cxx.checks");
check("cc_plus_one_cxx_asm", "cc_plus_one_cxx_asm.checks");
check("cc_plus_one_asm", "cc_plus_one_asm.checks");
// Check that C++ code compiled using the `cc` crate has the right mitigations.
check("cmake_plus_one_c", "cmake_plus_one_c.checks");
check("cmake_plus_one_c_asm", "cmake_plus_one_c_asm.checks");
check("cmake_plus_one_c_global_asm", "cmake_plus_one_c_global_asm.checks");
@ -71,8 +85,7 @@ fn check(func_re: &str, mut checks: &str) {
.input("enclave/target/x86_64-fortanix-unknown-sgx/debug/enclave")
.args(&["--demangle", &format!("--disassemble-symbols={func}")])
.run()
.stdout_utf8();
let dump = dump.as_bytes();
.stdout();
// Unique case, must succeed at one of two possible tests.
// This is because frame pointers are optional, and them being enabled requires

View file

@ -2,67 +2,67 @@
//@ normalize-stderr: "nightly|beta|1\.[0-9][0-9]\.[0-9]" -> "$$CHANNEL"
//! [struct@m!()] //~ WARN: unmatched disambiguator `struct` and suffix `!()`
//! [struct@m!{}]
//! [struct@m!{}] //~ WARN: unmatched disambiguator `struct` and suffix `!{}`
//! [struct@m![]]
//! [struct@f()] //~ WARN: unmatched disambiguator `struct` and suffix `()`
//! [struct@m!] //~ WARN: unmatched disambiguator `struct` and suffix `!`
//!
//! [enum@m!()] //~ WARN: unmatched disambiguator `enum` and suffix `!()`
//! [enum@m!{}]
//! [enum@m!{}] //~ WARN: unmatched disambiguator `enum` and suffix `!{}`
//! [enum@m![]]
//! [enum@f()] //~ WARN: unmatched disambiguator `enum` and suffix `()`
//! [enum@m!] //~ WARN: unmatched disambiguator `enum` and suffix `!`
//!
//! [trait@m!()] //~ WARN: unmatched disambiguator `trait` and suffix `!()`
//! [trait@m!{}]
//! [trait@m!{}] //~ WARN: unmatched disambiguator `trait` and suffix `!{}`
//! [trait@m![]]
//! [trait@f()] //~ WARN: unmatched disambiguator `trait` and suffix `()`
//! [trait@m!] //~ WARN: unmatched disambiguator `trait` and suffix `!`
//!
//! [module@m!()] //~ WARN: unmatched disambiguator `module` and suffix `!()`
//! [module@m!{}]
//! [module@m!{}] //~ WARN: unmatched disambiguator `module` and suffix `!{}`
//! [module@m![]]
//! [module@f()] //~ WARN: unmatched disambiguator `module` and suffix `()`
//! [module@m!] //~ WARN: unmatched disambiguator `module` and suffix `!`
//!
//! [mod@m!()] //~ WARN: unmatched disambiguator `mod` and suffix `!()`
//! [mod@m!{}]
//! [mod@m!{}] //~ WARN: unmatched disambiguator `mod` and suffix `!{}`
//! [mod@m![]]
//! [mod@f()] //~ WARN: unmatched disambiguator `mod` and suffix `()`
//! [mod@m!] //~ WARN: unmatched disambiguator `mod` and suffix `!`
//!
//! [const@m!()] //~ WARN: unmatched disambiguator `const` and suffix `!()`
//! [const@m!{}]
//! [const@m!{}] //~ WARN: unmatched disambiguator `const` and suffix `!{}`
//! [const@m![]]
//! [const@f()] //~ WARN: incompatible link kind for `f`
//! [const@m!] //~ WARN: unmatched disambiguator `const` and suffix `!`
//!
//! [constant@m!()] //~ WARN: unmatched disambiguator `constant` and suffix `!()`
//! [constant@m!{}]
//! [constant@m!{}] //~ WARN: unmatched disambiguator `constant` and suffix `!{}`
//! [constant@m![]]
//! [constant@f()] //~ WARN: incompatible link kind for `f`
//! [constant@m!] //~ WARN: unmatched disambiguator `constant` and suffix `!`
//!
//! [static@m!()] //~ WARN: unmatched disambiguator `static` and suffix `!()`
//! [static@m!{}]
//! [static@m!{}] //~ WARN: unmatched disambiguator `static` and suffix `!{}`
//! [static@m![]]
//! [static@f()] //~ WARN: incompatible link kind for `f`
//! [static@m!] //~ WARN: unmatched disambiguator `static` and suffix `!`
//!
//! [function@m!()] //~ WARN: unmatched disambiguator `function` and suffix `!()`
//! [function@m!{}]
//! [function@m!{}] //~ WARN: unmatched disambiguator `function` and suffix `!{}`
//! [function@m![]]
//! [function@f()]
//! [function@m!] //~ WARN: unmatched disambiguator `function` and suffix `!`
//!
//! [fn@m!()] //~ WARN: unmatched disambiguator `fn` and suffix `!()`
//! [fn@m!{}]
//! [fn@m!{}] //~ WARN: unmatched disambiguator `fn` and suffix `!{}`
//! [fn@m![]]
//! [fn@f()]
//! [fn@m!] //~ WARN: unmatched disambiguator `fn` and suffix `!`
//!
//! [method@m!()] //~ WARN: unmatched disambiguator `method` and suffix `!()`
//! [method@m!{}]
//! [method@m!{}] //~ WARN: unmatched disambiguator `method` and suffix `!{}`
//! [method@m![]]
//! [method@f()]
//! [method@m!] //~ WARN: unmatched disambiguator `method` and suffix `!`
@ -74,13 +74,13 @@
//! [derive@m!] //~ WARN: incompatible link kind for `m`
//!
//! [type@m!()] //~ WARN: unmatched disambiguator `type` and suffix `!()`
//! [type@m!{}]
//! [type@m!{}] //~ WARN: unmatched disambiguator `type` and suffix `!{}`
//! [type@m![]]
//! [type@f()] //~ WARN: unmatched disambiguator `type` and suffix `()`
//! [type@m!] //~ WARN: unmatched disambiguator `type` and suffix `!`
//!
//! [value@m!()] //~ WARN: unmatched disambiguator `value` and suffix `!()`
//! [value@m!{}]
//! [value@m!{}] //~ WARN: unmatched disambiguator `value` and suffix `!{}`
//! [value@m![]]
//! [value@f()]
//! [value@m!] //~ WARN: unmatched disambiguator `value` and suffix `!`
@ -92,13 +92,13 @@
//! [macro@m!]
//!
//! [prim@m!()] //~ WARN: unmatched disambiguator `prim` and suffix `!()`
//! [prim@m!{}]
//! [prim@m!{}] //~ WARN: unmatched disambiguator `prim` and suffix `!{}`
//! [prim@m![]]
//! [prim@f()] //~ WARN: unmatched disambiguator `prim` and suffix `()`
//! [prim@m!] //~ WARN: unmatched disambiguator `prim` and suffix `!`
//!
//! [primitive@m!()] //~ WARN: unmatched disambiguator `primitive` and suffix `!()`
//! [primitive@m!{}]
//! [primitive@m!{}] //~ WARN: unmatched disambiguator `primitive` and suffix `!{}`
//! [primitive@m![]]
//! [primitive@f()] //~ WARN: unmatched disambiguator `primitive` and suffix `()`
//! [primitive@m!] //~ WARN: unmatched disambiguator `primitive` and suffix `!`

View file

@ -7,6 +7,14 @@ LL | //! [struct@m!()]
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
= note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
warning: unmatched disambiguator `struct` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:5:6
|
LL | //! [struct@m!{}]
| ^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `struct` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:7:6
|
@ -31,6 +39,14 @@ LL | //! [enum@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `enum` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:11:6
|
LL | //! [enum@m!{}]
| ^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `enum` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:13:6
|
@ -55,6 +71,14 @@ LL | //! [trait@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `trait` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:17:6
|
LL | //! [trait@m!{}]
| ^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `trait` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:19:6
|
@ -79,6 +103,14 @@ LL | //! [module@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `module` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:23:6
|
LL | //! [module@m!{}]
| ^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `module` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:25:6
|
@ -103,6 +135,14 @@ LL | //! [mod@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `mod` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:29:6
|
LL | //! [mod@m!{}]
| ^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `mod` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:31:6
|
@ -127,6 +167,14 @@ LL | //! [const@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `const` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:35:6
|
LL | //! [const@m!{}]
| ^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: incompatible link kind for `f`
--> $DIR/disambiguator-endswith-named-suffix.rs:37:6
|
@ -155,6 +203,14 @@ LL | //! [constant@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `constant` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:41:6
|
LL | //! [constant@m!{}]
| ^^^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: incompatible link kind for `f`
--> $DIR/disambiguator-endswith-named-suffix.rs:43:6
|
@ -183,6 +239,14 @@ LL | //! [static@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `static` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:47:6
|
LL | //! [static@m!{}]
| ^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: incompatible link kind for `f`
--> $DIR/disambiguator-endswith-named-suffix.rs:49:6
|
@ -211,6 +275,14 @@ LL | //! [function@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `function` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:53:6
|
LL | //! [function@m!{}]
| ^^^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `function` and suffix `!`
--> $DIR/disambiguator-endswith-named-suffix.rs:56:6
|
@ -227,6 +299,14 @@ LL | //! [fn@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `fn` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:59:6
|
LL | //! [fn@m!{}]
| ^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `fn` and suffix `!`
--> $DIR/disambiguator-endswith-named-suffix.rs:62:6
|
@ -243,6 +323,14 @@ LL | //! [method@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `method` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:65:6
|
LL | //! [method@m!{}]
| ^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `method` and suffix `!`
--> $DIR/disambiguator-endswith-named-suffix.rs:68:6
|
@ -303,6 +391,14 @@ LL | //! [type@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `type` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:77:6
|
LL | //! [type@m!{}]
| ^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `type` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:79:6
|
@ -327,6 +423,14 @@ LL | //! [value@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `value` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:83:6
|
LL | //! [value@m!{}]
| ^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `value` and suffix `!`
--> $DIR/disambiguator-endswith-named-suffix.rs:86:6
|
@ -351,6 +455,14 @@ LL | //! [prim@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `prim` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:95:6
|
LL | //! [prim@m!{}]
| ^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `prim` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:97:6
|
@ -375,6 +487,14 @@ LL | //! [primitive@m!()]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `primitive` and suffix `!{}`
--> $DIR/disambiguator-endswith-named-suffix.rs:101:6
|
LL | //! [primitive@m!{}]
| ^^^^^^^^^
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: unmatched disambiguator `primitive` and suffix `()`
--> $DIR/disambiguator-endswith-named-suffix.rs:103:6
|
@ -391,5 +511,5 @@ LL | //! [primitive@m!]
|
= note: see https://doc.rust-lang.org/$CHANNEL/rustdoc/write-documentation/linking-to-items-by-name.html#namespaces-and-disambiguators for more info about disambiguators
warning: 46 warnings emitted
warning: 61 warnings emitted

View file

@ -0,0 +1,15 @@
#![no_std]
#![deny(rustdoc::broken_intra_doc_links)]
// regression test for https://github.com/rust-lang/rust/issues/54191
/// this is not a link to [`example.com`] //~ERROR unresolved link
///
/// this link [`has spaces in it`].
///
/// attempted link to method: [`Foo.bar()`] //~ERROR unresolved link
///
/// classic broken intra-doc link: [`Bar`] //~ERROR unresolved link
///
/// no backticks, so we let this one slide: [Foo.bar()]
pub struct Foo;

View file

@ -0,0 +1,31 @@
error: unresolved link to `example.com`
--> $DIR/bad-intra-doc.rs:6:29
|
LL | /// this is not a link to [`example.com`]
| ^^^^^^^^^^^ no item named `example.com` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
note: the lint level is defined here
--> $DIR/bad-intra-doc.rs:2:9
|
LL | #![deny(rustdoc::broken_intra_doc_links)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unresolved link to `Foo.bar`
--> $DIR/bad-intra-doc.rs:10:33
|
LL | /// attempted link to method: [`Foo.bar()`]
| ^^^^^^^^^ no item named `Foo.bar` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `Bar`
--> $DIR/bad-intra-doc.rs:12:38
|
LL | /// classic broken intra-doc link: [`Bar`]
| ^^^ no item named `Bar` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: aborting due to 3 previous errors

View file

@ -65,7 +65,7 @@ pub struct XLinkToCloneWithStartSpace;
/// [x][struct@Clone ] //~ERROR link
pub struct XLinkToCloneWithEndSpace;
/// [x][Clone\(\)] not URL-shaped enough
/// [x][Clone\(\)] //~ERROR link
pub struct XLinkToCloneWithEscapedParens;
/// [x][`Clone`] not URL-shaped enough

View file

@ -123,6 +123,14 @@ LL - /// [x][struct@Clone ]
LL + /// [x][trait@Clone ]
|
error: unresolved link to `Clone\(\)`
--> $DIR/weird-syntax.rs:68:9
|
LL | /// [x][Clone\(\)]
| ^^^^^^^^^ no item named `Clone\(\)` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: unresolved link to `Clone`
--> $DIR/weird-syntax.rs:74:9
|
@ -299,5 +307,5 @@ LL | /// - [`SDL_PROP_WINDOW_CREATE_COCOA_WINDOW_POINTER`]: the
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
error: aborting due to 27 previous errors
error: aborting due to 28 previous errors

View file

@ -1,18 +1,24 @@
//@ check-pass
/// [`…foo`] [`…bar`] [`Err`]
/// [`…foo`] //~ WARN: unresolved link
/// [`…bar`] //~ WARN: unresolved link
/// [`Err`]
pub struct Broken {}
/// [`…`] [`…`] [`Err`]
/// [`…`] //~ WARN: unresolved link
/// [`…`] //~ WARN: unresolved link
/// [`Err`]
pub struct Broken2 {}
/// [`…`][…] [`…`][…] [`Err`]
/// [`…`][…] //~ WARN: unresolved link
/// [`…`][…] //~ WARN: unresolved link
/// [`Err`]
pub struct Broken3 {}
/// […………………………][Broken3]
pub struct Broken4 {}
/// [Broken3][…………………………]
/// [Broken3][…………………………] //~ WARN: unresolved link
pub struct Broken5 {}
pub struct Err;

View file

@ -0,0 +1,59 @@
warning: unresolved link to `…foo`
--> $DIR/redundant_explicit_links-utf8.rs:3:7
|
LL | /// [`…foo`]
| ^^^^ no item named `…foo` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
= note: `#[warn(rustdoc::broken_intra_doc_links)]` on by default
warning: unresolved link to `…bar`
--> $DIR/redundant_explicit_links-utf8.rs:4:7
|
LL | /// [`…bar`]
| ^^^^ no item named `…bar` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: unresolved link to `…`
--> $DIR/redundant_explicit_links-utf8.rs:8:7
|
LL | /// [`…`]
| ^ no item named `…` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: unresolved link to `…`
--> $DIR/redundant_explicit_links-utf8.rs:9:7
|
LL | /// [`…`]
| ^ no item named `…` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: unresolved link to `…`
--> $DIR/redundant_explicit_links-utf8.rs:13:11
|
LL | /// [`…`][…]
| ^ no item named `…` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: unresolved link to `…`
--> $DIR/redundant_explicit_links-utf8.rs:14:11
|
LL | /// [`…`][…]
| ^ no item named `…` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: unresolved link to `…………………………`
--> $DIR/redundant_explicit_links-utf8.rs:21:15
|
LL | /// [Broken3][…………………………]
| ^^^^^^^^^^ no item named `…………………………` in scope
|
= help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]`
warning: 7 warnings emitted

View file

@ -1,7 +1,7 @@
#![crate_name = "foo"]
//@ has 'foo/fn.f.html'
//@ has - //*[@'class="rust item-decl"]' '#[export_name = "f"] pub fn f()'
#[export_name = "\
f"]
//@ has - //*[@'class="rust item-decl"]' '#[unsafe(export_name = "f")] pub fn f()'
#[unsafe(export_name = "\
f")]
pub fn f() {}

View file

@ -0,0 +1,14 @@
//@ edition: 2021
#![crate_name = "foo"]
//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
#[no_mangle]
pub extern "C" fn f() {}
//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "bar")]'
#[export_name = "bar"]
pub extern "C" fn g() {}
//@ has foo/fn.example.html '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".text")]'
#[link_section = ".text"]
pub extern "C" fn example() {}

View file

@ -0,0 +1,13 @@
// Tests that attributes are correctly copied onto a re-exported item.
//@ edition:2024
#![crate_name = "re_export"]
//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
pub use thingymod::thingy as thingy2;
mod thingymod {
#[unsafe(no_mangle)]
pub fn thingy() {
}
}

View file

@ -2,7 +2,7 @@
//@ edition:2021
#![crate_name = "re_export"]
//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[no_mangle]'
//@ has 're_export/fn.thingy2.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
pub use thingymod::thingy as thingy2;
mod thingymod {

View file

@ -1,13 +1,18 @@
//@ edition: 2024
#![crate_name = "foo"]
//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[no_mangle]'
#[no_mangle]
//@ has foo/fn.f.html '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
#[unsafe(no_mangle)]
pub extern "C" fn f() {}
//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[export_name = "bar"]'
#[export_name = "bar"]
//@ has foo/fn.g.html '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "bar")]'
#[unsafe(export_name = "bar")]
pub extern "C" fn g() {}
//@ has foo/fn.example.html '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".text")]'
#[unsafe(link_section = ".text")]
pub extern "C" fn example() {}
//@ has foo/struct.Repr.html '//pre[@class="rust item-decl"]' '#[repr(C, align(8))]'
#[repr(C, align(8))]
pub struct Repr;

View file

@ -4,13 +4,13 @@
extern crate reexports_attrs;
//@ has 'foo/fn.f0.html' '//pre[@class="rust item-decl"]' '#[no_mangle]'
//@ has 'foo/fn.f0.html' '//pre[@class="rust item-decl"]' '#[unsafe(no_mangle)]'
pub use reexports_attrs::f0;
//@ has 'foo/fn.f1.html' '//pre[@class="rust item-decl"]' '#[link_section = ".here"]'
//@ has 'foo/fn.f1.html' '//pre[@class="rust item-decl"]' '#[unsafe(link_section = ".here")]'
pub use reexports_attrs::f1;
//@ has 'foo/fn.f2.html' '//pre[@class="rust item-decl"]' '#[export_name = "f2export"]'
//@ has 'foo/fn.f2.html' '//pre[@class="rust item-decl"]' '#[unsafe(export_name = "f2export")]'
pub use reexports_attrs::f2;
//@ has 'foo/enum.T0.html' '//pre[@class="rust item-decl"]' '#[repr(u8)]'

View file

@ -0,0 +1,10 @@
#![feature(explicit_tail_calls)]
#![expect(incomplete_features)]
const _: () = f();
const fn f() {
become f(); //~ error: constant evaluation is taking a long time
}
fn main() {}

View file

@ -0,0 +1,17 @@
error: constant evaluation is taking a long time
--> $DIR/infinite-recursion-in-ctfe.rs:7:5
|
LL | become f();
| ^^^^^^^^^^
|
= note: this lint makes sure the compiler doesn't get stuck due to infinite loops in const eval.
If your compilation actually takes a long time, you can safely allow the lint.
help: the constant being evaluated
--> $DIR/infinite-recursion-in-ctfe.rs:4:1
|
LL | const _: () = f();
| ^^^^^^^^^^^
= note: `#[deny(long_running_const_eval)]` on by default
error: aborting due to 1 previous error

View file

@ -4,6 +4,7 @@
//@ ignore-emscripten no weak symbol support
//@ ignore-apple no extern_weak linkage
//@ ignore-aix no extern_weak linkage
//@ aux-build:lib.rs

View file

@ -24,3 +24,24 @@ fn const_continue_to_block() -> u8 {
}
}
}
fn const_continue_to_shadowed_block() -> u8 {
let state = 0;
#[loop_match]
loop {
state = 'blk: {
match state {
0 => {
#[const_continue]
break 'blk 1;
}
_ => 'blk: {
//~^ WARN label name `'blk` shadows a label name that is already in scope
#[const_continue]
break 'blk 2;
//~^ ERROR `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
}
}
}
}
}

View file

@ -1,8 +1,23 @@
warning: label name `'blk` shadows a label name that is already in scope
--> $DIR/const-continue-to-block.rs:38:22
|
LL | state = 'blk: {
| ---- first declared here
...
LL | _ => 'blk: {
| ^^^^ label `'blk` already in scope
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-to-block.rs:20:27
|
LL | break 'b 2;
| ^^
error: aborting due to 1 previous error
error: `#[const_continue]` must break to a labeled block that participates in a `#[loop_match]`
--> $DIR/const-continue-to-block.rs:41:27
|
LL | break 'blk 2;
| ^^^^
error: aborting due to 2 previous errors; 1 warning emitted

View file

@ -142,6 +142,25 @@ fn break_without_value_unit() {
}
}
fn break_without_label() {
let mut state = State::A;
let _ = {
#[loop_match]
loop {
state = 'blk: {
match state {
_ => {
#[const_continue]
break State::A;
//~^ ERROR unlabeled `break` inside of a labeled block
//~| ERROR a `#[const_continue]` must break to a label with a value
}
}
}
}
};
}
fn arm_has_guard(cond: bool) {
let mut state = State::A;
#[loop_match]

View file

@ -9,6 +9,12 @@ help: give the `break` a value of the expected type
LL | break 'blk /* value */;
| +++++++++++
error[E0695]: unlabeled `break` inside of a labeled block
--> $DIR/invalid.rs:154:25
|
LL | break State::A;
| ^^^^^^^^^^^^^^ `break` statements that would diverge to or through a labeled block need to bear a label
error: invalid update of the `#[loop_match]` state
--> $DIR/invalid.rs:18:9
|
@ -80,14 +86,20 @@ error: a `#[const_continue]` must break to a label with a value
LL | break 'blk;
| ^^^^^^^^^^
error: a `#[const_continue]` must break to a label with a value
--> $DIR/invalid.rs:154:25
|
LL | break State::A;
| ^^^^^^^^^^^^^^
error: match arms that are part of a `#[loop_match]` cannot have guards
--> $DIR/invalid.rs:155:29
--> $DIR/invalid.rs:174:29
|
LL | State::B if cond => break 'a,
| ^^^^
error[E0004]: non-exhaustive patterns: `State::B` and `State::C` not covered
--> $DIR/invalid.rs:168:19
--> $DIR/invalid.rs:187:19
|
LL | match state {
| ^^^^^ patterns `State::B` and `State::C` not covered
@ -110,12 +122,12 @@ LL ~ State::B | State::C => todo!(),
|
error[E0579]: lower range bound must be less than upper
--> $DIR/invalid.rs:185:17
--> $DIR/invalid.rs:204:17
|
LL | 4.0..3.0 => {
| ^^^^^^^^
error: aborting due to 14 previous errors
error: aborting due to 16 previous errors
Some errors have detailed explanations: E0004, E0308, E0579.
Some errors have detailed explanations: E0004, E0308, E0579, E0695.
For more information about an error, try `rustc --explain E0004`.