Auto merge of #147745 - matthiaskrgr:rollup-b4kftk9, r=matthiaskrgr

Rollup of 11 pull requests

Successful merges:

 - rust-lang/rust#143191 (Stabilize `rwlock_downgrade` library feature)
 - rust-lang/rust#147444 (Allow printing a fully-qualified path in `def_path_str`)
 - rust-lang/rust#147527 (Update t-compiler beta nomination Zulip msg)
 - rust-lang/rust#147670 (some `ErrorGuaranteed` cleanups)
 - rust-lang/rust#147676 (Return spans out of `is_doc_comment` to reduce reliance on `.span()` on attributes)
 - rust-lang/rust#147708 (const `mem::drop`)
 - rust-lang/rust#147710 (Fix ICE when using contracts on async functions)
 - rust-lang/rust#147716 (Fix some comments)
 - rust-lang/rust#147718 (miri: use allocator_shim_contents codegen helper)
 - rust-lang/rust#147729 (ignore boring locals when explaining why a borrow contains a point due to drop of a live local under polonius)
 - rust-lang/rust#147742 (Revert unintentional whitespace changes to rustfmt-excluded file)

r? `@ghost`
`@rustbot` modify labels: rollup
This commit is contained in:
bors 2025-10-15 23:20:53 +00:00
commit 402ce0ef07
40 changed files with 422 additions and 374 deletions

View file

@ -86,10 +86,10 @@ impl AttributeExt for Attribute {
/// Returns `true` if it is a sugared doc comment (`///` or `//!` for example). /// Returns `true` if it is a sugared doc comment (`///` or `//!` for example).
/// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not /// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not
/// a doc comment) will return `false`. /// a doc comment) will return `false`.
fn is_doc_comment(&self) -> bool { fn is_doc_comment(&self) -> Option<Span> {
match self.kind { match self.kind {
AttrKind::Normal(..) => false, AttrKind::Normal(..) => None,
AttrKind::DocComment(..) => true, AttrKind::DocComment(..) => Some(self.span),
} }
} }
@ -776,7 +776,7 @@ pub trait AttributeExt: Debug {
/// Returns `true` if it is a sugared doc comment (`///` or `//!` for example). /// Returns `true` if it is a sugared doc comment (`///` or `//!` for example).
/// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not /// So `#[doc = "doc"]` (which is a doc comment) and `#[doc(...)]` (which is not
/// a doc comment) will return `false`. /// a doc comment) will return `false`.
fn is_doc_comment(&self) -> bool; fn is_doc_comment(&self) -> Option<Span>;
#[inline] #[inline]
fn has_name(&self, name: Symbol) -> bool { fn has_name(&self, name: Symbol) -> bool {
@ -863,8 +863,9 @@ impl Attribute {
AttributeExt::path_matches(self, name) AttributeExt::path_matches(self, name)
} }
// on ast attributes we return a bool since that's what most code already expects
pub fn is_doc_comment(&self) -> bool { pub fn is_doc_comment(&self) -> bool {
AttributeExt::is_doc_comment(self) AttributeExt::is_doc_comment(self).is_some()
} }
#[inline] #[inline]

View file

@ -31,10 +31,23 @@ pub enum AllocatorTy {
Usize, Usize,
} }
/// Some allocator methods are known to the compiler: they act more like
/// intrinsics/language primitives than library-defined functions.
/// FIXME: ideally this would be derived from attributes like `#[rustc_allocator]`,
/// so we don't have two sources of truth.
#[derive(Copy, Clone, Debug)]
pub enum SpecialAllocatorMethod {
Alloc,
AllocZeroed,
Dealloc,
Realloc,
}
/// A method that will be codegened in the allocator shim. /// A method that will be codegened in the allocator shim.
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct AllocatorMethod { pub struct AllocatorMethod {
pub name: Symbol, pub name: Symbol,
pub special: Option<SpecialAllocatorMethod>,
pub inputs: &'static [AllocatorMethodInput], pub inputs: &'static [AllocatorMethodInput],
pub output: AllocatorTy, pub output: AllocatorTy,
} }
@ -47,11 +60,13 @@ pub struct AllocatorMethodInput {
pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[ pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[
AllocatorMethod { AllocatorMethod {
name: sym::alloc, name: sym::alloc,
special: Some(SpecialAllocatorMethod::Alloc),
inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }], inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }],
output: AllocatorTy::ResultPtr, output: AllocatorTy::ResultPtr,
}, },
AllocatorMethod { AllocatorMethod {
name: sym::dealloc, name: sym::dealloc,
special: Some(SpecialAllocatorMethod::Dealloc),
inputs: &[ inputs: &[
AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr }, AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr },
AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }, AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout },
@ -60,6 +75,7 @@ pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[
}, },
AllocatorMethod { AllocatorMethod {
name: sym::realloc, name: sym::realloc,
special: Some(SpecialAllocatorMethod::Realloc),
inputs: &[ inputs: &[
AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr }, AllocatorMethodInput { name: "ptr", ty: AllocatorTy::Ptr },
AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }, AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout },
@ -69,6 +85,7 @@ pub static ALLOCATOR_METHODS: &[AllocatorMethod] = &[
}, },
AllocatorMethod { AllocatorMethod {
name: sym::alloc_zeroed, name: sym::alloc_zeroed,
special: Some(SpecialAllocatorMethod::AllocZeroed),
inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }], inputs: &[AllocatorMethodInput { name: "layout", ty: AllocatorTy::Layout }],
output: AllocatorTy::ResultPtr, output: AllocatorTy::ResultPtr,
}, },

View file

@ -302,7 +302,7 @@ pub(crate) trait CombineAttributeParser<S: Stage>: 'static {
type Item; type Item;
/// A function that converts individual items (of type [`Item`](Self::Item)) into the final attribute. /// A function that converts individual items (of type [`Item`](Self::Item)) into the final attribute.
/// ///
/// For example, individual representations fomr `#[repr(...)]` attributes into an `AttributeKind::Repr(x)`, /// For example, individual representations from `#[repr(...)]` attributes into an `AttributeKind::Repr(x)`,
/// where `x` is a vec of these individual reprs. /// where `x` is a vec of these individual reprs.
const CONVERT: ConvertFn<Self::Item>; const CONVERT: ConvertFn<Self::Item>;

View file

@ -28,7 +28,8 @@ pub fn parse_version(s: Symbol) -> Option<RustcVersion> {
} }
pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool { pub fn is_builtin_attr(attr: &impl AttributeExt) -> bool {
attr.is_doc_comment() || attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name)) attr.is_doc_comment().is_some()
|| attr.ident().is_some_and(|ident| is_builtin_attr_name(ident.name))
} }
pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>( pub fn is_doc_alias_attrs_contain_symbol<'tcx, T: AttributeExt + 'tcx>(

View file

@ -687,7 +687,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
} }
} }
Some(Cause::DropVar(local, location)) => { Some(Cause::DropVar(local, location)) if !is_local_boring(local) => {
let mut should_note_order = false; let mut should_note_order = false;
if self.local_name(local).is_some() if self.local_name(local).is_some()
&& let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place && let Some((WriteKind::StorageDeadOrDrop, place)) = kind_place
@ -705,7 +705,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
} }
} }
Some(Cause::LiveVar(..)) | None => { Some(Cause::LiveVar(..) | Cause::DropVar(..)) | None => {
// Here, under NLL: no cause was found. Under polonius: no cause was found, or a // Here, under NLL: no cause was found. Under polonius: no cause was found, or a
// boring local was found, which we ignore like NLLs do to match its diagnostics. // boring local was found, which we ignore like NLLs do to match its diagnostics.
if let Some(region) = self.to_error_region_vid(borrow_region_vid) { if let Some(region) = self.to_error_region_vid(borrow_region_vid) {

View file

@ -71,6 +71,14 @@ fn expand_contract_clause(
.span_err(attr_span, "contract annotations can only be used on functions")); .span_err(attr_span, "contract annotations can only be used on functions"));
} }
// Contracts are not yet supported on async/gen functions
if new_tts.iter().any(|tt| is_kw(tt, kw::Async) || is_kw(tt, kw::Gen)) {
return Err(ecx.sess.dcx().span_err(
attr_span,
"contract annotations are not yet supported on async or gen functions",
));
}
// Found the `fn` keyword, now find either the `where` token or the function body. // Found the `fn` keyword, now find either the `where` token or the function body.
let next_tt = loop { let next_tt = loop {
let Some(tt) = cursor.next() else { let Some(tt) = cursor.next() else {

View file

@ -1,13 +1,13 @@
use libc::c_uint; use libc::c_uint;
use rustc_ast::expand::allocator::{ use rustc_ast::expand::allocator::{
AllocatorMethod, AllocatorTy, NO_ALLOC_SHIM_IS_UNSTABLE, default_fn_name, global_fn_name, AllocatorMethod, AllocatorTy, NO_ALLOC_SHIM_IS_UNSTABLE, SpecialAllocatorMethod,
default_fn_name, global_fn_name,
}; };
use rustc_codegen_ssa::traits::BaseTypeCodegenMethods as _; use rustc_codegen_ssa::traits::BaseTypeCodegenMethods as _;
use rustc_middle::bug; use rustc_middle::bug;
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs}; use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrFlags, CodegenFnAttrs};
use rustc_middle::ty::TyCtxt; use rustc_middle::ty::TyCtxt;
use rustc_session::config::{DebugInfo, OomStrategy}; use rustc_session::config::{DebugInfo, OomStrategy};
use rustc_span::sym;
use rustc_symbol_mangling::mangle_internal_symbol; use rustc_symbol_mangling::mangle_internal_symbol;
use crate::attributes::llfn_attrs_from_instance; use crate::attributes::llfn_attrs_from_instance;
@ -65,12 +65,12 @@ pub(crate) unsafe fn codegen(
let from_name = mangle_internal_symbol(tcx, &global_fn_name(method.name)); let from_name = mangle_internal_symbol(tcx, &global_fn_name(method.name));
let to_name = mangle_internal_symbol(tcx, &default_fn_name(method.name)); let to_name = mangle_internal_symbol(tcx, &default_fn_name(method.name));
let alloc_attr_flag = match method.name { let alloc_attr_flag = match method.special {
sym::alloc => CodegenFnAttrFlags::ALLOCATOR, Some(SpecialAllocatorMethod::Alloc) => CodegenFnAttrFlags::ALLOCATOR,
sym::dealloc => CodegenFnAttrFlags::DEALLOCATOR, Some(SpecialAllocatorMethod::Dealloc) => CodegenFnAttrFlags::DEALLOCATOR,
sym::realloc => CodegenFnAttrFlags::REALLOCATOR, Some(SpecialAllocatorMethod::Realloc) => CodegenFnAttrFlags::REALLOCATOR,
sym::alloc_zeroed => CodegenFnAttrFlags::ALLOCATOR_ZEROED, Some(SpecialAllocatorMethod::AllocZeroed) => CodegenFnAttrFlags::ALLOCATOR_ZEROED,
_ => CodegenFnAttrFlags::empty(), None => CodegenFnAttrFlags::empty(),
}; };
let mut attrs = CodegenFnAttrs::new(); let mut attrs = CodegenFnAttrs::new();

View file

@ -669,6 +669,7 @@ pub fn allocator_shim_contents(tcx: TyCtxt<'_>, kind: AllocatorKind) -> Vec<Allo
if tcx.alloc_error_handler_kind(()).unwrap() == AllocatorKind::Default { if tcx.alloc_error_handler_kind(()).unwrap() == AllocatorKind::Default {
methods.push(AllocatorMethod { methods.push(AllocatorMethod {
name: ALLOC_ERROR_HANDLER, name: ALLOC_ERROR_HANDLER,
special: None,
inputs: &[], inputs: &[],
output: AllocatorTy::Never, output: AllocatorTy::Never,
}); });

View file

@ -289,7 +289,7 @@ enum NodeState<N, S, A: Annotation> {
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
enum WalkReturn<S, A: Annotation> { enum WalkReturn<S, A: Annotation> {
/// The walk found a cycle, but the entire component is not known to have /// The walk found a cycle, but the entire component is not known to have
/// been fully walked yet. We only know the minimum depth of this /// been fully walked yet. We only know the minimum depth of this
/// component in a minimum spanning tree of the graph. This component /// component in a minimum spanning tree of the graph. This component
/// is tentatively represented by the state of the first node of this /// is tentatively represented by the state of the first node of this
/// cycle we met, which is at `min_depth`. /// cycle we met, which is at `min_depth`.

View file

@ -1304,8 +1304,12 @@ impl AttributeExt for Attribute {
} }
#[inline] #[inline]
fn is_doc_comment(&self) -> bool { fn is_doc_comment(&self) -> Option<Span> {
matches!(self, Attribute::Parsed(AttributeKind::DocComment { .. })) if let Attribute::Parsed(AttributeKind::DocComment { span, .. }) = self {
Some(*span)
} else {
None
}
} }
#[inline] #[inline]
@ -1423,7 +1427,7 @@ impl Attribute {
} }
#[inline] #[inline]
pub fn is_doc_comment(&self) -> bool { pub fn is_doc_comment(&self) -> Option<Span> {
AttributeExt::is_doc_comment(self) AttributeExt::is_doc_comment(self)
} }

View file

@ -1651,9 +1651,7 @@ fn check_method_receiver<'tcx>(
// If the receiver already has errors reported, consider it valid to avoid // If the receiver already has errors reported, consider it valid to avoid
// unnecessary errors (#58712). // unnecessary errors (#58712).
if receiver_ty.references_error() { receiver_ty.error_reported()?;
return Ok(());
}
let arbitrary_self_types_level = if tcx.features().arbitrary_self_types_pointers() { let arbitrary_self_types_level = if tcx.features().arbitrary_self_types_pointers() {
Some(ArbitrarySelfTypesLevel::WithPointers) Some(ArbitrarySelfTypesLevel::WithPointers)

View file

@ -39,9 +39,7 @@ fn check_impl<'tcx>(
// Skip impls where one of the self type is an error type. // Skip impls where one of the self type is an error type.
// This occurs with e.g., resolve failures (#30589). // This occurs with e.g., resolve failures (#30589).
if trait_ref.references_error() { trait_ref.error_reported()?;
return Ok(());
}
enforce_trait_manually_implementable(tcx, impl_def_id, trait_ref.def_id, trait_def) enforce_trait_manually_implementable(tcx, impl_def_id, trait_ref.def_id, trait_def)
.and(enforce_empty_impls_for_marker_traits(tcx, impl_def_id, trait_ref.def_id, trait_def)) .and(enforce_empty_impls_for_marker_traits(tcx, impl_def_id, trait_ref.def_id, trait_def))
@ -188,9 +186,9 @@ fn check_object_overlap<'tcx>(
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
let trait_def_id = trait_ref.def_id; let trait_def_id = trait_ref.def_id;
if trait_ref.references_error() { if let Err(guar) = trait_ref.error_reported() {
debug!("coherence: skipping impl {:?} with error {:?}", impl_def_id, trait_ref); debug!("coherence: skipping impl {:?} with error {:?}", impl_def_id, trait_ref);
return Ok(()); return Err(guar);
} }
// check for overlap with the automatic `impl Trait for dyn Trait` // check for overlap with the automatic `impl Trait for dyn Trait`

View file

@ -76,20 +76,10 @@ pub(crate) fn enforce_impl_lifetime_params_are_constrained(
impl_def_id: LocalDefId, impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity(); let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
if impl_self_ty.references_error() {
// Don't complain about unconstrained type params when self ty isn't known due to errors. // Don't complain about unconstrained type params when self ty isn't known due to errors.
// (#36836) // (#36836)
tcx.dcx().span_delayed_bug( impl_self_ty.error_reported()?;
tcx.def_span(impl_def_id),
format!(
"potentially unconstrained type parameters weren't evaluated: {impl_self_ty:?}",
),
);
// This is super fishy, but our current `rustc_hir_analysis::check_crate` pipeline depends on
// `type_of` having been called much earlier, and thus this value being read from cache.
// Compilation must continue in order for other important diagnostics to keep showing up.
return Ok(());
}
let impl_generics = tcx.generics_of(impl_def_id); let impl_generics = tcx.generics_of(impl_def_id);
let impl_predicates = tcx.predicates_of(impl_def_id); let impl_predicates = tcx.predicates_of(impl_def_id);
@ -174,20 +164,11 @@ pub(crate) fn enforce_impl_non_lifetime_params_are_constrained(
impl_def_id: LocalDefId, impl_def_id: LocalDefId,
) -> Result<(), ErrorGuaranteed> { ) -> Result<(), ErrorGuaranteed> {
let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity(); let impl_self_ty = tcx.type_of(impl_def_id).instantiate_identity();
if impl_self_ty.references_error() {
// Don't complain about unconstrained type params when self ty isn't known due to errors. // Don't complain about unconstrained type params when self ty isn't known due to errors.
// (#36836) // (#36836)
tcx.dcx().span_delayed_bug( impl_self_ty.error_reported()?;
tcx.def_span(impl_def_id),
format!(
"potentially unconstrained type parameters weren't evaluated: {impl_self_ty:?}",
),
);
// This is super fishy, but our current `rustc_hir_analysis::check_crate` pipeline depends on
// `type_of` having been called much earlier, and thus this value being read from cache.
// Compilation must continue in order for other important diagnostics to keep showing up.
return Ok(());
}
let impl_generics = tcx.generics_of(impl_def_id); let impl_generics = tcx.generics_of(impl_def_id);
let impl_predicates = tcx.predicates_of(impl_def_id); let impl_predicates = tcx.predicates_of(impl_def_id);
let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity); let impl_trait_ref = tcx.impl_trait_ref(impl_def_id).map(ty::EarlyBinder::instantiate_identity);

View file

@ -685,9 +685,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}); });
let ty = let ty =
self.check_expr_with_expectation_and_needs(oprnd, hint, Needs::maybe_mut_place(mutbl)); self.check_expr_with_expectation_and_needs(oprnd, hint, Needs::maybe_mut_place(mutbl));
if let Err(guar) = ty.error_reported() {
return Ty::new_error(self.tcx, guar);
}
match kind { match kind {
_ if ty.references_error() => Ty::new_misc_error(self.tcx),
hir::BorrowKind::Raw => { hir::BorrowKind::Raw => {
self.check_named_place_expr(oprnd); self.check_named_place_expr(oprnd);
Ty::new_ptr(self.tcx, ty, mutbl) Ty::new_ptr(self.tcx, ty, mutbl)

View file

@ -393,7 +393,7 @@ pub struct MissingDoc;
impl_lint_pass!(MissingDoc => [MISSING_DOCS]); impl_lint_pass!(MissingDoc => [MISSING_DOCS]);
fn has_doc(attr: &hir::Attribute) -> bool { fn has_doc(attr: &hir::Attribute) -> bool {
if attr.is_doc_comment() { if attr.is_doc_comment().is_some() {
return true; return true;
} }

View file

@ -31,6 +31,7 @@ use crate::ty::{
thread_local! { thread_local! {
static FORCE_IMPL_FILENAME_LINE: Cell<bool> = const { Cell::new(false) }; static FORCE_IMPL_FILENAME_LINE: Cell<bool> = const { Cell::new(false) };
static SHOULD_PREFIX_WITH_CRATE_NAME: Cell<bool> = const { Cell::new(false) };
static SHOULD_PREFIX_WITH_CRATE: Cell<bool> = const { Cell::new(false) }; static SHOULD_PREFIX_WITH_CRATE: Cell<bool> = const { Cell::new(false) };
static NO_TRIMMED_PATH: Cell<bool> = const { Cell::new(false) }; static NO_TRIMMED_PATH: Cell<bool> = const { Cell::new(false) };
static FORCE_TRIMMED_PATH: Cell<bool> = const { Cell::new(false) }; static FORCE_TRIMMED_PATH: Cell<bool> = const { Cell::new(false) };
@ -98,7 +99,18 @@ define_helper!(
/// cycle errors, this can result in extra or suboptimal error output, /// cycle errors, this can result in extra or suboptimal error output,
/// so this variable disables that check. /// so this variable disables that check.
fn with_forced_impl_filename_line(ForcedImplGuard, FORCE_IMPL_FILENAME_LINE); fn with_forced_impl_filename_line(ForcedImplGuard, FORCE_IMPL_FILENAME_LINE);
/// Adds the crate name prefix to paths where appropriate.
/// Unlike `with_crate_prefix`, this unconditionally uses `tcx.crate_name` instead of sometimes
/// using `crate::` for local items.
///
/// Overrides `with_crate_prefix`.
// This function is not currently used in-tree, but it's used by a downstream rustc-driver in
// Ferrocene. Please check with them before removing it.
fn with_resolve_crate_name(CrateNamePrefixGuard, SHOULD_PREFIX_WITH_CRATE_NAME);
/// Adds the `crate::` prefix to paths where appropriate. /// Adds the `crate::` prefix to paths where appropriate.
///
/// Ignored if `with_resolve_crate_name` is active.
fn with_crate_prefix(CratePrefixGuard, SHOULD_PREFIX_WITH_CRATE); fn with_crate_prefix(CratePrefixGuard, SHOULD_PREFIX_WITH_CRATE);
/// Prevent path trimming if it is turned on. Path trimming affects `Display` impl /// Prevent path trimming if it is turned on. Path trimming affects `Display` impl
/// of various rustc types, for example `std::vec::Vec` would be trimmed to `Vec`, /// of various rustc types, for example `std::vec::Vec` would be trimmed to `Vec`,
@ -2313,7 +2325,7 @@ impl<'tcx> Printer<'tcx> for FmtPrinter<'_, 'tcx> {
fn print_crate_name(&mut self, cnum: CrateNum) -> Result<(), PrintError> { fn print_crate_name(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
self.empty_path = true; self.empty_path = true;
if cnum == LOCAL_CRATE { if cnum == LOCAL_CRATE && !with_resolve_crate_name() {
if self.tcx.sess.at_least_rust_2018() { if self.tcx.sess.at_least_rust_2018() {
// We add the `crate::` keyword on Rust 2018, only when desired. // We add the `crate::` keyword on Rust 2018, only when desired.
if with_crate_prefix() { if with_crate_prefix() {

View file

@ -22,7 +22,7 @@ impl<'a> HashStable<StableHashingContext<'a>> for [hir::Attribute] {
let filtered: SmallVec<[&hir::Attribute; 8]> = self let filtered: SmallVec<[&hir::Attribute; 8]> = self
.iter() .iter()
.filter(|attr| { .filter(|attr| {
!attr.is_doc_comment() attr.is_doc_comment().is_none()
// FIXME(jdonszelmann) have a better way to handle ignored attrs // FIXME(jdonszelmann) have a better way to handle ignored attrs
&& !attr.ident().is_some_and(|ident| hcx.is_ignored_attr(ident.name)) && !attr.ident().is_some_and(|ident| hcx.is_ignored_attr(ident.name))
}) })

View file

@ -216,8 +216,7 @@ pub fn attrs_to_doc_fragments<'a, A: AttributeExt + Clone + 'a>(
for (attr, item_id) in attrs { for (attr, item_id) in attrs {
if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() { if let Some((doc_str, comment_kind)) = attr.doc_str_and_comment_kind() {
let doc = beautify_doc_string(doc_str, comment_kind); let doc = beautify_doc_string(doc_str, comment_kind);
let (span, kind, from_expansion) = if attr.is_doc_comment() { let (span, kind, from_expansion) = if let Some(span) = attr.is_doc_comment() {
let span = attr.span();
(span, DocFragmentKind::SugaredDoc, span.from_expansion()) (span, DocFragmentKind::SugaredDoc, span.from_expansion())
} else { } else {
let attr_span = attr.span(); let attr_span = attr.span();

View file

@ -21,7 +21,7 @@ bitflags! {
const GENERALIZE_REPR_C = 2; const GENERALIZE_REPR_C = 2;
/// Normalizes integers for compatibility with Clang /// Normalizes integers for compatibility with Clang
/// `-fsanitize-cfi-icall-experimental-normalize-integers` option for cross-language LLVM /// `-fsanitize-cfi-icall-experimental-normalize-integers` option for cross-language LLVM
/// CFI and KCFI support. /// CFI and KCFI support.
const NORMALIZE_INTEGERS = 4; const NORMALIZE_INTEGERS = 4;
/// Do not perform self type erasure for attaching a secondary type id to methods with their /// Do not perform self type erasure for attaching a secondary type id to methods with their
/// concrete self so they can be used as function pointers. /// concrete self so they can be used as function pointers.

View file

@ -309,7 +309,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
pub struct OnUnimplementedFormatString { pub struct OnUnimplementedFormatString {
/// Symbol of the format string, i.e. `"content"` /// Symbol of the format string, i.e. `"content"`
symbol: Symbol, symbol: Symbol,
///The span of the format string, i.e. `"content"` /// The span of the format string, i.e. `"content"`
span: Span, span: Span,
is_diagnostic_namespace_variant: bool, is_diagnostic_namespace_variant: bool,
} }

View file

@ -6,7 +6,7 @@
#![stable(feature = "rust1", since = "1.0.0")] #![stable(feature = "rust1", since = "1.0.0")]
use crate::alloc::Layout; use crate::alloc::Layout;
use crate::marker::DiscriminantKind; use crate::marker::{Destruct, DiscriminantKind};
use crate::panic::const_assert; use crate::panic::const_assert;
use crate::{clone, cmp, fmt, hash, intrinsics, ptr}; use crate::{clone, cmp, fmt, hash, intrinsics, ptr};
@ -958,8 +958,13 @@ pub const fn replace<T>(dest: &mut T, src: T) -> T {
/// [`RefCell`]: crate::cell::RefCell /// [`RefCell`]: crate::cell::RefCell
#[inline] #[inline]
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_destruct", issue = "133214")]
#[rustc_diagnostic_item = "mem_drop"] #[rustc_diagnostic_item = "mem_drop"]
pub fn drop<T>(_x: T) {} pub const fn drop<T>(_x: T)
where
T: [const] Destruct,
{
}
/// Bitwise-copies a value. /// Bitwise-copies a value.
/// ///

View file

@ -700,7 +700,6 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// ///
/// ``` /// ```
/// #![feature(nonpoison_rwlock)] /// #![feature(nonpoison_rwlock)]
/// #![feature(rwlock_downgrade)]
/// ///
/// use std::sync::nonpoison::{RwLock, RwLockWriteGuard}; /// use std::sync::nonpoison::{RwLock, RwLockWriteGuard};
/// ///
@ -719,7 +718,6 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// ///
/// ``` /// ```
/// #![feature(nonpoison_rwlock)] /// #![feature(nonpoison_rwlock)]
/// #![feature(rwlock_downgrade)]
/// ///
/// use std::sync::Arc; /// use std::sync::Arc;
/// use std::sync::nonpoison::{RwLock, RwLockWriteGuard}; /// use std::sync::nonpoison::{RwLock, RwLockWriteGuard};
@ -752,8 +750,7 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// # let final_check = rw.read(); /// # let final_check = rw.read();
/// # assert_eq!(*final_check, 3); /// # assert_eq!(*final_check, 3);
/// ``` /// ```
#[unstable(feature = "rwlock_downgrade", issue = "128203")] #[unstable(feature = "nonpoison_rwlock", issue = "134645")]
// #[unstable(feature = "nonpoison_rwlock", issue = "134645")]
pub fn downgrade(s: Self) -> RwLockReadGuard<'rwlock, T> { pub fn downgrade(s: Self) -> RwLockReadGuard<'rwlock, T> {
let lock = s.lock; let lock = s.lock;

View file

@ -813,8 +813,6 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// `downgrade` takes ownership of the `RwLockWriteGuard` and returns a [`RwLockReadGuard`]. /// `downgrade` takes ownership of the `RwLockWriteGuard` and returns a [`RwLockReadGuard`].
/// ///
/// ``` /// ```
/// #![feature(rwlock_downgrade)]
///
/// use std::sync::{RwLock, RwLockWriteGuard}; /// use std::sync::{RwLock, RwLockWriteGuard};
/// ///
/// let rw = RwLock::new(0); /// let rw = RwLock::new(0);
@ -831,8 +829,6 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// thread calling `downgrade` and any reads it performs after downgrading. /// thread calling `downgrade` and any reads it performs after downgrading.
/// ///
/// ``` /// ```
/// #![feature(rwlock_downgrade)]
///
/// use std::sync::{Arc, RwLock, RwLockWriteGuard}; /// use std::sync::{Arc, RwLock, RwLockWriteGuard};
/// ///
/// let rw = Arc::new(RwLock::new(1)); /// let rw = Arc::new(RwLock::new(1));
@ -863,7 +859,7 @@ impl<'rwlock, T: ?Sized> RwLockWriteGuard<'rwlock, T> {
/// # let final_check = rw.read().unwrap(); /// # let final_check = rw.read().unwrap();
/// # assert_eq!(*final_check, 3); /// # assert_eq!(*final_check, 3);
/// ``` /// ```
#[unstable(feature = "rwlock_downgrade", issue = "128203")] #[stable(feature = "rwlock_downgrade", since = "CURRENT_RUSTC_VERSION")]
pub fn downgrade(s: Self) -> RwLockReadGuard<'rwlock, T> { pub fn downgrade(s: Self) -> RwLockReadGuard<'rwlock, T> {
let lock = s.lock; let lock = s.lock;

View file

@ -4,7 +4,6 @@
#![feature(once_cell_try)] #![feature(once_cell_try)]
#![feature(lock_value_accessors)] #![feature(lock_value_accessors)]
#![feature(reentrant_lock)] #![feature(reentrant_lock)]
#![feature(rwlock_downgrade)]
#![feature(std_internals)] #![feature(std_internals)]
#![feature(sync_nonpoison)] #![feature(sync_nonpoison)]
#![feature(nonpoison_condvar)] #![feature(nonpoison_condvar)]

View file

@ -2719,7 +2719,7 @@ fn add_without_unwanted_attributes<'hir>(
import_parent: Option<DefId>, import_parent: Option<DefId>,
) { ) {
for attr in new_attrs { for attr in new_attrs {
if attr.is_doc_comment() { if attr.is_doc_comment().is_some() {
attrs.push((Cow::Borrowed(attr), import_parent)); attrs.push((Cow::Borrowed(attr), import_parent));
continue; continue;
} }

View file

@ -65,7 +65,7 @@ fn filter_non_cfg_tokens_from_list(args_tokens: &TokenStream) -> Vec<TokenTree>
/// it and put them into `attrs`. /// it and put them into `attrs`.
fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) { fn add_only_cfg_attributes(attrs: &mut Vec<Attribute>, new_attrs: &[Attribute]) {
for attr in new_attrs { for attr in new_attrs {
if attr.is_doc_comment() { if attr.is_doc_comment().is_some() {
continue; continue;
} }
let mut attr = attr.clone(); let mut attr = attr.clone();

View file

@ -47,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for FourForwardSlashes {
.tcx .tcx
.hir_attrs(item.hir_id()) .hir_attrs(item.hir_id())
.iter() .iter()
.filter(|i| i.is_doc_comment()) .filter(|i| i.is_doc_comment().is_some())
.fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span())); .fold(item.span.shrink_to_lo(), |span, attr| span.to(attr.span()));
let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else { let (Some(file), _, _, end_line, _) = sm.span_to_location_info(span) else {
return; return;

View file

@ -475,7 +475,7 @@ fn block_has_safety_comment(cx: &LateContext<'_>, span: Span) -> bool {
fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span { fn include_attrs_in_span(cx: &LateContext<'_>, hir_id: HirId, span: Span) -> Span {
span.to(cx.tcx.hir_attrs(hir_id).iter().fold(span, |acc, attr| { span.to(cx.tcx.hir_attrs(hir_id).iter().fold(span, |acc, attr| {
if attr.is_doc_comment() { if attr.is_doc_comment().is_some() {
return acc; return acc;
} }
acc.to(attr.span()) acc.to(attr.span())

View file

@ -523,16 +523,16 @@ where
} }
}, },
TraceStyle::Async => Some( TraceStyle::Async => Some(
span.scope() span.scope()
.from_root() .from_root()
.take(1) .take(1)
.next() .next()
.unwrap_or(span) .unwrap_or(span)
.id() .id()
.into_u64() .into_u64()
.cast_signed() // the comment above explains the cast .cast_signed() // the comment above explains the cast
), ),
} }
} }
fn enter_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) { fn enter_span(&self, span: SpanRef<S>, ts: f64, tid: usize, out: &Sender<Message>) {
@ -567,11 +567,11 @@ where
Some(thread_data) => (thread_data, false), Some(thread_data) => (thread_data, false),
None => { None => {
let tid = self.max_tid.fetch_add(1, Ordering::SeqCst); let tid = self.max_tid.fetch_add(1, Ordering::SeqCst);
let out = self.out.lock().unwrap().clone(); let out = self.out.lock().unwrap().clone();
let start = TracingChromeInstant::setup_for_thread_and_start(tid); let start = TracingChromeInstant::setup_for_thread_and_start(tid);
*thread_data = Some(ThreadData { tid, out, start }); *thread_data = Some(ThreadData { tid, out, start });
(thread_data.as_mut().unwrap(), true) (thread_data.as_mut().unwrap(), true)
} }
}; };
start.with_elapsed_micros_subtracting_tracing(|ts| { start.with_elapsed_micros_subtracting_tracing(|ts| {
@ -583,7 +583,7 @@ where
let _ignored = out.send(Message::NewThread(*tid, name)); let _ignored = out.send(Message::NewThread(*tid, name));
} }
f(ts, *tid, out); f(ts, *tid, out);
}); });
}); });
} }
} }
@ -605,15 +605,15 @@ where
fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) { fn on_record(&self, id: &span::Id, values: &span::Record<'_>, ctx: Context<'_, S>) {
if self.include_args { if self.include_args {
self.with_elapsed_micros_subtracting_tracing(|_, _, _| { self.with_elapsed_micros_subtracting_tracing(|_, _, _| {
let span = ctx.span(id).unwrap(); let span = ctx.span(id).unwrap();
let mut exts = span.extensions_mut(); let mut exts = span.extensions_mut();
let args = exts.get_mut::<ArgsWrapper>(); let args = exts.get_mut::<ArgsWrapper>();
if let Some(args) = args { if let Some(args) = args {
let args = Arc::make_mut(&mut args.args); let args = Arc::make_mut(&mut args.args);
values.record(&mut JsonVisitor { object: args }); values.record(&mut JsonVisitor { object: args });
} }
}); });
} }
} }
@ -636,16 +636,16 @@ where
fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { fn on_new_span(&self, attrs: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) {
self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| { self.with_elapsed_micros_subtracting_tracing(|ts, tid, out| {
if self.include_args { if self.include_args {
let mut args = Object::new(); let mut args = Object::new();
attrs.record(&mut JsonVisitor { object: &mut args }); attrs.record(&mut JsonVisitor { object: &mut args });
ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper { ctx.span(id).unwrap().extensions_mut().insert(ArgsWrapper {
args: Arc::new(args), args: Arc::new(args),
}); });
} }
if let TraceStyle::Threaded = self.trace_style { if let TraceStyle::Threaded = self.trace_style {
return; return;
} }
self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out); self.enter_span(ctx.span(id).expect("Span not found."), ts, tid, out);
}); });

View file

@ -38,47 +38,47 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
for i in 0..dest_len { for i in 0..dest_len {
let op = this.read_immediate(&this.project_index(&op, i)?)?; let op = this.read_immediate(&this.project_index(&op, i)?)?;
let dest = this.project_index(&dest, i)?; let dest = this.project_index(&dest, i)?;
let ty::Float(float_ty) = op.layout.ty.kind() else { let ty::Float(float_ty) = op.layout.ty.kind() else {
span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name) span_bug!(this.cur_span(), "{} operand is not a float", intrinsic_name)
}; };
// Using host floats except for sqrt (but it's fine, these operations do not // Using host floats except for sqrt (but it's fine, these operations do not
// have guaranteed precision). // have guaranteed precision).
let val = match float_ty { let val = match float_ty {
FloatTy::F16 => unimplemented!("f16_f128"), FloatTy::F16 => unimplemented!("f16_f128"),
FloatTy::F32 => { FloatTy::F32 => {
let f = op.to_scalar().to_f32()?; let f = op.to_scalar().to_f32()?;
let res = match intrinsic_name { let res = match intrinsic_name {
"fsqrt" => math::sqrt(f), "fsqrt" => math::sqrt(f),
"fsin" => f.to_host().sin().to_soft(), "fsin" => f.to_host().sin().to_soft(),
"fcos" => f.to_host().cos().to_soft(), "fcos" => f.to_host().cos().to_soft(),
"fexp" => f.to_host().exp().to_soft(), "fexp" => f.to_host().exp().to_soft(),
"fexp2" => f.to_host().exp2().to_soft(), "fexp2" => f.to_host().exp2().to_soft(),
"flog" => f.to_host().ln().to_soft(), "flog" => f.to_host().ln().to_soft(),
"flog2" => f.to_host().log2().to_soft(), "flog2" => f.to_host().log2().to_soft(),
"flog10" => f.to_host().log10().to_soft(), "flog10" => f.to_host().log10().to_soft(),
_ => bug!(), _ => bug!(),
};
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
FloatTy::F64 => {
let f = op.to_scalar().to_f64()?;
let res = match intrinsic_name {
"fsqrt" => math::sqrt(f),
"fsin" => f.to_host().sin().to_soft(),
"fcos" => f.to_host().cos().to_soft(),
"fexp" => f.to_host().exp().to_soft(),
"fexp2" => f.to_host().exp2().to_soft(),
"flog" => f.to_host().ln().to_soft(),
"flog2" => f.to_host().log2().to_soft(),
"flog10" => f.to_host().log10().to_soft(),
_ => bug!(),
};
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
FloatTy::F128 => unimplemented!("f16_f128"),
}; };
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
FloatTy::F64 => {
let f = op.to_scalar().to_f64()?;
let res = match intrinsic_name {
"fsqrt" => math::sqrt(f),
"fsin" => f.to_host().sin().to_soft(),
"fcos" => f.to_host().cos().to_soft(),
"fexp" => f.to_host().exp().to_soft(),
"fexp2" => f.to_host().exp2().to_soft(),
"flog" => f.to_host().ln().to_soft(),
"flog2" => f.to_host().log2().to_soft(),
"flog10" => f.to_host().log10().to_soft(),
_ => bug!(),
};
let res = this.adjust_nan(res, &[f]);
Scalar::from(res)
}
FloatTy::F128 => unimplemented!("f16_f128"),
};
this.write_scalar(val, &dest)?; this.write_scalar(val, &dest)?;
} }

View file

@ -53,6 +53,7 @@
extern crate rustc_abi; extern crate rustc_abi;
extern crate rustc_apfloat; extern crate rustc_apfloat;
extern crate rustc_ast; extern crate rustc_ast;
extern crate rustc_codegen_ssa;
extern crate rustc_const_eval; extern crate rustc_const_eval;
extern crate rustc_data_structures; extern crate rustc_data_structures;
extern crate rustc_errors; extern crate rustc_errors;

View file

@ -12,6 +12,8 @@ use rand::rngs::StdRng;
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use rustc_abi::{Align, ExternAbi, Size}; use rustc_abi::{Align, ExternAbi, Size};
use rustc_apfloat::{Float, FloatConvert}; use rustc_apfloat::{Float, FloatConvert};
use rustc_ast::expand::allocator::{self, SpecialAllocatorMethod};
use rustc_data_structures::either::Either;
use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::fx::{FxHashMap, FxHashSet};
#[allow(unused)] #[allow(unused)]
use rustc_data_structures::static_assert_size; use rustc_data_structures::static_assert_size;
@ -27,6 +29,7 @@ use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
use rustc_session::config::InliningThreshold; use rustc_session::config::InliningThreshold;
use rustc_span::def_id::{CrateNum, DefId}; use rustc_span::def_id::{CrateNum, DefId};
use rustc_span::{Span, SpanData, Symbol}; use rustc_span::{Span, SpanData, Symbol};
use rustc_symbol_mangling::mangle_internal_symbol;
use rustc_target::callconv::FnAbi; use rustc_target::callconv::FnAbi;
use crate::alloc_addresses::EvalContextExt; use crate::alloc_addresses::EvalContextExt;
@ -652,6 +655,10 @@ pub struct MiriMachine<'tcx> {
pub(crate) pthread_rwlock_sanity: Cell<bool>, pub(crate) pthread_rwlock_sanity: Cell<bool>,
pub(crate) pthread_condvar_sanity: Cell<bool>, pub(crate) pthread_condvar_sanity: Cell<bool>,
/// (Foreign) symbols that are synthesized as part of the allocator shim: the key indicates the
/// name of the symbol being synthesized; the value indicates whether this should invoke some
/// other symbol or whether this has special allocator semantics.
pub(crate) allocator_shim_symbols: FxHashMap<Symbol, Either<Symbol, SpecialAllocatorMethod>>,
/// Cache for `mangle_internal_symbol`. /// Cache for `mangle_internal_symbol`.
pub(crate) mangle_internal_symbol_cache: FxHashMap<&'static str, String>, pub(crate) mangle_internal_symbol_cache: FxHashMap<&'static str, String>,
@ -819,6 +826,7 @@ impl<'tcx> MiriMachine<'tcx> {
pthread_mutex_sanity: Cell::new(false), pthread_mutex_sanity: Cell::new(false),
pthread_rwlock_sanity: Cell::new(false), pthread_rwlock_sanity: Cell::new(false),
pthread_condvar_sanity: Cell::new(false), pthread_condvar_sanity: Cell::new(false),
allocator_shim_symbols: Self::allocator_shim_symbols(tcx),
mangle_internal_symbol_cache: Default::default(), mangle_internal_symbol_cache: Default::default(),
force_intrinsic_fallback: config.force_intrinsic_fallback, force_intrinsic_fallback: config.force_intrinsic_fallback,
float_nondet: config.float_nondet, float_nondet: config.float_nondet,
@ -827,6 +835,36 @@ impl<'tcx> MiriMachine<'tcx> {
} }
} }
fn allocator_shim_symbols(
tcx: TyCtxt<'tcx>,
) -> FxHashMap<Symbol, Either<Symbol, SpecialAllocatorMethod>> {
use rustc_codegen_ssa::base::allocator_shim_contents;
// codegen uses `allocator_kind_for_codegen` here, but that's only needed to deal with
// dylibs which we do not support.
let Some(kind) = tcx.allocator_kind(()) else {
return Default::default();
};
let methods = allocator_shim_contents(tcx, kind);
let mut symbols = FxHashMap::default();
for method in methods {
let from_name = Symbol::intern(&mangle_internal_symbol(
tcx,
&allocator::global_fn_name(method.name),
));
let to = match method.special {
Some(special) => Either::Right(special),
None =>
Either::Left(Symbol::intern(&mangle_internal_symbol(
tcx,
&allocator::default_fn_name(method.name),
))),
};
symbols.try_insert(from_name, to).unwrap();
}
symbols
}
pub(crate) fn late_init( pub(crate) fn late_init(
ecx: &mut MiriInterpCx<'tcx>, ecx: &mut MiriInterpCx<'tcx>,
config: &MiriConfig, config: &MiriConfig,
@ -992,6 +1030,7 @@ impl VisitProvenance for MiriMachine<'_> {
pthread_mutex_sanity: _, pthread_mutex_sanity: _,
pthread_rwlock_sanity: _, pthread_rwlock_sanity: _,
pthread_condvar_sanity: _, pthread_condvar_sanity: _,
allocator_shim_symbols: _,
mangle_internal_symbol_cache: _, mangle_internal_symbol_cache: _,
force_intrinsic_fallback: _, force_intrinsic_fallback: _,
float_nondet: _, float_nondet: _,

View file

@ -1,5 +1,8 @@
use rustc_abi::{Align, Size}; use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size};
use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::expand::allocator::SpecialAllocatorMethod;
use rustc_middle::ty::Ty;
use rustc_span::Symbol;
use rustc_target::callconv::FnAbi;
use crate::*; use crate::*;
@ -54,30 +57,100 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
Align::from_bytes(prev_power_of_two(size)).unwrap() Align::from_bytes(prev_power_of_two(size)).unwrap()
} }
/// Emulates calling the internal __rust_* allocator functions /// Check some basic requirements for this allocation request:
fn emulate_allocator( /// non-zero size, power-of-two alignment.
fn check_rust_alloc_request(&self, size: u64, align: u64) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
if size == 0 {
throw_ub_format!("creating allocation with size 0");
}
if size > this.max_size_of_val().bytes() {
throw_ub_format!("creating an allocation larger than half the address space");
}
if let Err(e) = Align::from_bytes(align) {
match e {
AlignFromBytesError::TooLarge(_) => {
throw_unsup_format!(
"creating allocation with alignment {align} exceeding rustc's maximum \
supported value"
);
}
AlignFromBytesError::NotPowerOfTwo(_) => {
throw_ub_format!("creating allocation with non-power-of-two alignment {align}");
}
}
}
interp_ok(())
}
fn rust_special_allocator_method(
&mut self, &mut self,
default: impl FnOnce(&mut MiriInterpCx<'tcx>) -> InterpResult<'tcx>, method: SpecialAllocatorMethod,
) -> InterpResult<'tcx, EmulateItemResult> { link_name: Symbol,
abi: &FnAbi<'tcx, Ty<'tcx>>,
args: &[OpTy<'tcx>],
dest: &PlaceTy<'tcx>,
) -> InterpResult<'tcx> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
let Some(allocator_kind) = this.tcx.allocator_kind(()) else { match method {
// in real code, this symbol does not exist without an allocator SpecialAllocatorMethod::Alloc | SpecialAllocatorMethod::AllocZeroed => {
return interp_ok(EmulateItemResult::NotSupported); let [size, align] =
}; this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let size = this.read_target_usize(size)?;
let align = this.read_target_usize(align)?;
match allocator_kind { this.check_rust_alloc_request(size, align)?;
AllocatorKind::Global => {
// When `#[global_allocator]` is used, `__rust_*` is defined by the macro expansion let ptr = this.allocate_ptr(
// of this attribute. As such we have to call an exported Rust function, Size::from_bytes(size),
// and not execute any Miri shim. Somewhat unintuitively doing so is done Align::from_bytes(align).unwrap(),
// by returning `NotSupported`, which triggers the `lookup_exported_symbol` MiriMemoryKind::Rust.into(),
// fallback case in `emulate_foreign_item`. if matches!(method, SpecialAllocatorMethod::AllocZeroed) {
interp_ok(EmulateItemResult::NotSupported) AllocInit::Zero
} else {
AllocInit::Uninit
},
)?;
this.write_pointer(ptr, dest)
} }
AllocatorKind::Default => { SpecialAllocatorMethod::Dealloc => {
default(this)?; let [ptr, old_size, align] =
interp_ok(EmulateItemResult::NeedsReturn) this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_target_usize(old_size)?;
let align = this.read_target_usize(align)?;
// No need to check old_size/align; we anyway check that they match the allocation.
this.deallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
MiriMemoryKind::Rust.into(),
)
}
SpecialAllocatorMethod::Realloc => {
let [ptr, old_size, align, new_size] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_target_usize(old_size)?;
let align = this.read_target_usize(align)?;
let new_size = this.read_target_usize(new_size)?;
// No need to check old_size; we anyway check that they match the allocation.
this.check_rust_alloc_request(new_size, align)?;
let align = Align::from_bytes(align).unwrap();
let new_ptr = this.reallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), align)),
Size::from_bytes(new_size),
align,
MiriMemoryKind::Rust.into(),
AllocInit::Uninit,
)?;
this.write_pointer(new_ptr, dest)
} }
} }
} }

View file

@ -2,8 +2,9 @@ use std::collections::hash_map::Entry;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
use rustc_abi::{Align, AlignFromBytesError, CanonAbi, Size}; use rustc_abi::{Align, CanonAbi, Size};
use rustc_ast::expand::allocator::AllocatorKind; use rustc_ast::expand::allocator::NO_ALLOC_SHIM_IS_UNSTABLE;
use rustc_data_structures::either::Either;
use rustc_hir::attrs::Linkage; use rustc_hir::attrs::Linkage;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_hir::def_id::CrateNum; use rustc_hir::def_id::CrateNum;
@ -11,6 +12,7 @@ use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
use rustc_middle::mir::interpret::AllocInit; use rustc_middle::mir::interpret::AllocInit;
use rustc_middle::ty::{Instance, Ty}; use rustc_middle::ty::{Instance, Ty};
use rustc_middle::{mir, ty}; use rustc_middle::{mir, ty};
use rustc_session::config::OomStrategy;
use rustc_span::Symbol; use rustc_span::Symbol;
use rustc_target::callconv::FnAbi; use rustc_target::callconv::FnAbi;
@ -50,31 +52,21 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> { ) -> InterpResult<'tcx, Option<(&'tcx mir::Body<'tcx>, ty::Instance<'tcx>)>> {
let this = self.eval_context_mut(); let this = self.eval_context_mut();
// Some shims forward to other MIR bodies. // Handle allocator shim.
match link_name.as_str() { if let Some(shim) = this.machine.allocator_shim_symbols.get(&link_name) {
// This allocator function has forwarding shims synthesized during normal codegen match *shim {
// (see `allocator_shim_contents`); this is where we emulate that behavior. Either::Left(other_fn) => {
// FIXME should use global_fn_name, but mangle_internal_symbol requires a static str.
name if name == this.mangle_internal_symbol("__rust_alloc_error_handler") => {
// Forward to the right symbol that implements this function.
let Some(handler_kind) = this.tcx.alloc_error_handler_kind(()) else {
// in real code, this symbol does not exist without an allocator
throw_unsup_format!(
"`__rust_alloc_error_handler` cannot be called when no alloc error handler is set"
);
};
if handler_kind == AllocatorKind::Default {
let name =
Symbol::intern(this.mangle_internal_symbol("__rdl_alloc_error_handler"));
let handler = this let handler = this
.lookup_exported_symbol(name)? .lookup_exported_symbol(other_fn)?
.expect("missing alloc error handler symbol"); .expect("missing alloc error handler symbol");
return interp_ok(Some(handler)); return interp_ok(Some(handler));
} }
// Fall through to the `lookup_exported_symbol` below which should find Either::Right(special) => {
// a `__rust_alloc_error_handler`. this.rust_special_allocator_method(special, link_name, abi, args, dest)?;
this.return_to_block(ret)?;
return interp_ok(None);
}
} }
_ => {}
} }
// FIXME: avoid allocating memory // FIXME: avoid allocating memory
@ -254,33 +246,6 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {} impl<'tcx> EvalContextExtPriv<'tcx> for crate::MiriInterpCx<'tcx> {}
trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> { trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
/// Check some basic requirements for this allocation request:
/// non-zero size, power-of-two alignment.
fn check_rustc_alloc_request(&self, size: u64, align: u64) -> InterpResult<'tcx> {
let this = self.eval_context_ref();
if size == 0 {
throw_ub_format!("creating allocation with size 0");
}
if size > this.max_size_of_val().bytes() {
throw_ub_format!("creating an allocation larger than half the address space");
}
if let Err(e) = Align::from_bytes(align) {
match e {
AlignFromBytesError::TooLarge(_) => {
throw_unsup_format!(
"creating allocation with alignment {align} exceeding rustc's maximum \
supported value"
);
}
AlignFromBytesError::NotPowerOfTwo(_) => {
throw_ub_format!("creating allocation with non-power-of-two alignment {align}");
}
}
}
interp_ok(())
}
fn emulate_foreign_item_inner( fn emulate_foreign_item_inner(
&mut self, &mut self,
link_name: Symbol, link_name: Symbol,
@ -340,7 +305,51 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Here we dispatch all the shims for foreign functions. If you have a platform specific // Here we dispatch all the shims for foreign functions. If you have a platform specific
// shim, add it to the corresponding submodule. // shim, add it to the corresponding submodule.
match link_name.as_str() { match link_name.as_str() {
// Magic functions Rust emits (and not as part of the allocator shim).
name if name == this.mangle_internal_symbol(NO_ALLOC_SHIM_IS_UNSTABLE) => {
// This is a no-op shim that only exists to prevent making the allocator shims
// instantly stable.
let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
}
name if name == this.mangle_internal_symbol(OomStrategy::SYMBOL) => {
// Gets the value of the `oom` option.
let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let val = this.tcx.sess.opts.unstable_opts.oom.should_panic();
this.write_int(val, dest)?;
}
// Miri-specific extern functions // Miri-specific extern functions
"miri_alloc" => {
let [size, align] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let size = this.read_target_usize(size)?;
let align = this.read_target_usize(align)?;
this.check_rust_alloc_request(size, align)?;
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::Miri.into(),
AllocInit::Uninit,
)?;
this.write_pointer(ptr, dest)?;
}
"miri_dealloc" => {
let [ptr, old_size, align] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_target_usize(old_size)?;
let align = this.read_target_usize(align)?;
// No need to check old_size/align; we anyway check that they match the allocation.
this.deallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
MiriMemoryKind::Miri.into(),
)?;
}
"miri_start_unwind" => { "miri_start_unwind" => {
let [payload] = let [payload] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?; this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
@ -492,7 +501,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
} }
} }
} }
// GenMC mode: Assume statements block the current thread when their condition is false. // GenMC mode: Assume statements block the current thread when their condition is false.
"miri_genmc_assume" => { "miri_genmc_assume" => {
let [condition] = let [condition] =
@ -579,133 +587,6 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
} }
} }
// Rust allocation
name if name == this.mangle_internal_symbol("__rust_alloc") || name == "miri_alloc" => {
let default = |ecx: &mut MiriInterpCx<'tcx>| {
// Only call `check_shim` when `#[global_allocator]` isn't used. When that
// macro is used, we act like no shim exists, so that the exported function can run.
let [size, align] =
ecx.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let size = ecx.read_target_usize(size)?;
let align = ecx.read_target_usize(align)?;
ecx.check_rustc_alloc_request(size, align)?;
let memory_kind = match link_name.as_str() {
"miri_alloc" => MiriMemoryKind::Miri,
_ => MiriMemoryKind::Rust,
};
let ptr = ecx.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
memory_kind.into(),
AllocInit::Uninit,
)?;
ecx.write_pointer(ptr, dest)
};
match link_name.as_str() {
"miri_alloc" => {
default(this)?;
return interp_ok(EmulateItemResult::NeedsReturn);
}
_ => return this.emulate_allocator(default),
}
}
name if name == this.mangle_internal_symbol("__rust_alloc_zeroed") => {
return this.emulate_allocator(|this| {
// See the comment for `__rust_alloc` why `check_shim` is only called in the
// default case.
let [size, align] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let size = this.read_target_usize(size)?;
let align = this.read_target_usize(align)?;
this.check_rustc_alloc_request(size, align)?;
let ptr = this.allocate_ptr(
Size::from_bytes(size),
Align::from_bytes(align).unwrap(),
MiriMemoryKind::Rust.into(),
AllocInit::Zero,
)?;
this.write_pointer(ptr, dest)
});
}
name if name == this.mangle_internal_symbol("__rust_dealloc")
|| name == "miri_dealloc" =>
{
let default = |ecx: &mut MiriInterpCx<'tcx>| {
// See the comment for `__rust_alloc` why `check_shim` is only called in the
// default case.
let [ptr, old_size, align] =
ecx.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let ptr = ecx.read_pointer(ptr)?;
let old_size = ecx.read_target_usize(old_size)?;
let align = ecx.read_target_usize(align)?;
let memory_kind = match link_name.as_str() {
"miri_dealloc" => MiriMemoryKind::Miri,
_ => MiriMemoryKind::Rust,
};
// No need to check old_size/align; we anyway check that they match the allocation.
ecx.deallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), Align::from_bytes(align).unwrap())),
memory_kind.into(),
)
};
match link_name.as_str() {
"miri_dealloc" => {
default(this)?;
return interp_ok(EmulateItemResult::NeedsReturn);
}
_ => return this.emulate_allocator(default),
}
}
name if name == this.mangle_internal_symbol("__rust_realloc") => {
return this.emulate_allocator(|this| {
// See the comment for `__rust_alloc` why `check_shim` is only called in the
// default case.
let [ptr, old_size, align, new_size] =
this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let ptr = this.read_pointer(ptr)?;
let old_size = this.read_target_usize(old_size)?;
let align = this.read_target_usize(align)?;
let new_size = this.read_target_usize(new_size)?;
// No need to check old_size; we anyway check that they match the allocation.
this.check_rustc_alloc_request(new_size, align)?;
let align = Align::from_bytes(align).unwrap();
let new_ptr = this.reallocate_ptr(
ptr,
Some((Size::from_bytes(old_size), align)),
Size::from_bytes(new_size),
align,
MiriMemoryKind::Rust.into(),
AllocInit::Uninit,
)?;
this.write_pointer(new_ptr, dest)
});
}
name if name == this.mangle_internal_symbol("__rust_no_alloc_shim_is_unstable_v2") => {
// This is a no-op shim that only exists to prevent making the allocator shims instantly stable.
let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
}
name if name
== this.mangle_internal_symbol("__rust_alloc_error_handler_should_panic_v2") =>
{
// Gets the value of the `oom` option.
let [] = this.check_shim_sig_lenient(abi, CanonAbi::Rust, link_name, args)?;
let val = this.tcx.sess.opts.unstable_opts.oom.should_panic();
this.write_int(val, dest)?;
}
// C memory handling functions // C memory handling functions
"memcmp" => { "memcmp" => {
let [left, right, n] = let [left, right, n] =

View file

@ -0,0 +1,10 @@
//@ compile-flags: --crate-type=lib
//@ edition: 2021
#![feature(contracts)]
//~^ WARN the feature `contracts` is incomplete
#[core::contracts::ensures(|ret| *ret)]
//~^ ERROR contract annotations are not yet supported on async or gen functions
async fn _always_true(b: bool) -> bool {
b
}

View file

@ -0,0 +1,17 @@
error: contract annotations are not yet supported on async or gen functions
--> $DIR/async-fn-contract-ice-145333.rs:6:1
|
LL | #[core::contracts::ensures(|ret| *ret)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
warning: the feature `contracts` is incomplete and may not be safe to use and/or cause compiler crashes
--> $DIR/async-fn-contract-ice-145333.rs:3:12
|
LL | #![feature(contracts)]
| ^^^^^^^^^
|
= note: see issue #128044 <https://github.com/rust-lang/rust/issues/128044> for more information
= note: `#[warn(incomplete_features)]` on by default
error: aborting due to 1 previous error; 1 warning emitted

View file

@ -1,13 +1,15 @@
error[E0499]: cannot borrow `*t` as mutable more than once at a time error[E0499]: cannot borrow `*t` as mutable more than once at a time
--> $DIR/lending-iterator-sanity-checks.rs:19:19 --> $DIR/lending-iterator-sanity-checks.rs:19:19
| |
LL | fn use_live<T: LendingIterator>(t: &mut T) -> Option<(T::Item<'_>, T::Item<'_>)> {
| - let's call the lifetime of this reference `'1`
LL | let Some(i) = t.next() else { return None }; LL | let Some(i) = t.next() else { return None };
| - first mutable borrow occurs here | - first mutable borrow occurs here
LL | let Some(j) = t.next() else { return None }; LL | let Some(j) = t.next() else { return None };
| ^ second mutable borrow occurs here | ^ second mutable borrow occurs here
... ...
LL | } LL | Some((i, j))
| - first borrow might be used here, when `i` is dropped and runs the destructor for type `<T as LendingIterator>::Item<'_>` | ------------ returning this value requires that `*t` is borrowed for `'1`
error[E0499]: cannot borrow `*t` as mutable more than once at a time error[E0499]: cannot borrow `*t` as mutable more than once at a time
--> $DIR/lending-iterator-sanity-checks.rs:31:13 --> $DIR/lending-iterator-sanity-checks.rs:31:13

View file

@ -4,6 +4,10 @@
#![feature(trivial_bounds)] #![feature(trivial_bounds)]
// we use identity instead of drop because the presence of [const] Destruct means that there
// are additional bounds on the function, which result in additional errors
use std::convert::identity;
trait Foo { trait Foo {
type Item: Copy type Item: Copy
where where
@ -21,7 +25,7 @@ impl Foo for () {
fn main() { fn main() {
let x = String::from("hello, world"); let x = String::from("hello, world");
drop(<() as Foo>::copy_me(&x)); let _ = identity(<() as Foo>::copy_me(&x));
//~^ ERROR overflow evaluating the requirement `String <: <() as Foo>::Item` //~^ ERROR overflow evaluating the requirement `String <: <() as Foo>::Item`
//~| ERROR overflow evaluating the requirement `<() as Foo>::Item well-formed` //~| ERROR overflow evaluating the requirement `<() as Foo>::Item well-formed`
//~| ERROR overflow evaluating the requirement `&<() as Foo>::Item well-formed` //~| ERROR overflow evaluating the requirement `&<() as Foo>::Item well-formed`

View file

@ -1,11 +1,11 @@
error[E0275]: overflow evaluating the requirement `String: Copy` error[E0275]: overflow evaluating the requirement `String: Copy`
--> $DIR/alias-bound-unsound.rs:18:38 --> $DIR/alias-bound-unsound.rs:22:38
| |
LL | type Item = String where String: Copy; LL | type Item = String where String: Copy;
| ^^^^ | ^^^^
| |
note: the requirement `String: Copy` appears on the `impl`'s associated type `Item` but not on the corresponding trait's associated type note: the requirement `String: Copy` appears on the `impl`'s associated type `Item` but not on the corresponding trait's associated type
--> $DIR/alias-bound-unsound.rs:8:10 --> $DIR/alias-bound-unsound.rs:12:10
| |
LL | trait Foo { LL | trait Foo {
| --- in this trait | --- in this trait
@ -13,50 +13,50 @@ LL | type Item: Copy
| ^^^^ this trait's associated type doesn't have the requirement `String: Copy` | ^^^^ this trait's associated type doesn't have the requirement `String: Copy`
error[E0275]: overflow evaluating the requirement `String <: <() as Foo>::Item` error[E0275]: overflow evaluating the requirement `String <: <() as Foo>::Item`
--> $DIR/alias-bound-unsound.rs:24:31 --> $DIR/alias-bound-unsound.rs:28:43
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^ | ^^
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _` error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:24:10 --> $DIR/alias-bound-unsound.rs:28:22
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _` error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:24:10 --> $DIR/alias-bound-unsound.rs:28:22
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^
| |
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error[E0275]: overflow evaluating the requirement `&<() as Foo>::Item well-formed` error[E0275]: overflow evaluating the requirement `&<() as Foo>::Item well-formed`
--> $DIR/alias-bound-unsound.rs:24:31 --> $DIR/alias-bound-unsound.rs:28:43
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^ | ^^
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item well-formed` error[E0275]: overflow evaluating the requirement `<() as Foo>::Item well-formed`
--> $DIR/alias-bound-unsound.rs:24:10 --> $DIR/alias-bound-unsound.rs:28:22
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _` error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:24:10 --> $DIR/alias-bound-unsound.rs:28:22
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^^^^^^^^^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^
| |
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _` error[E0275]: overflow evaluating the requirement `<() as Foo>::Item == _`
--> $DIR/alias-bound-unsound.rs:24:31 --> $DIR/alias-bound-unsound.rs:28:43
| |
LL | drop(<() as Foo>::copy_me(&x)); LL | let _ = identity(<() as Foo>::copy_me(&x));
| ^^ | ^^
error: aborting due to 8 previous errors error: aborting due to 8 previous errors

View file

@ -738,13 +738,15 @@ message_on_reopen = "PR #{number} has been reopened. Pinging @*T-rustdoc*."
[notify-zulip."beta-nominated".compiler] [notify-zulip."beta-nominated".compiler]
required_labels = ["T-compiler"] required_labels = ["T-compiler"]
zulip_stream = 474880 # #t-compiler/backports zulip_stream = 474880 # #t-compiler/backports
topic = "#{number}: beta-nominated" topic = "#{number}: beta-backport nomination"
message_on_add = [ message_on_add = [
"""\ """\
@**channel** PR #{number} "{title}" has been nominated for beta backport. PR #{number} "{title}" fixes a regression.
{recipients}, please evaluate nominating this PR for backport.
The following poll is a vibe-check and not binding.
""", """,
"""\ """\
/poll Approve beta backport of #{number}? /poll Should #{number} be beta backported?
approve approve
decline decline
don't know don't know