Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
b429456
Move some methods that only need the region context onto that
oli-obk Jun 23, 2026
7ebb859
Consistently taint root cx
oli-obk Jun 24, 2026
f44093d
Remove some unnecessary mutabilities
oli-obk Jun 24, 2026
7102a6c
Typeck children always have the same param env as their parent
oli-obk Jun 24, 2026
7cb94f0
Don't compute opaque types for typeck children. They are always the s…
oli-obk Jun 24, 2026
7ccc34f
Add a convenience helper for doing type ops
oli-obk Jun 24, 2026
b2a46c9
Move `check_ffi_pure` into the attribute parser
obeis Jun 24, 2026
c19c462
Use `NoArgsAttributeParser` for parsing `#[ffi_pure]`
obeis Jun 24, 2026
ba2c9ba
std: truncate thread names on NetBSD
joboet Jun 25, 2026
6b9de05
Attribute docs `deprecated` , `warn`, `allow`, `cfg`, `deny`, and `fo…
kantnero Jun 26, 2026
9b188dd
cg_LLVM: Stop needing an alloca for volatile loads
scottmcm May 23, 2026
6d6fcc9
add regression test for clone suggestion empty obligations
TaKO8Ki Jun 26, 2026
f08762c
guard clone suggestion against empty obligation errors
TaKO8Ki Jun 26, 2026
c200d2f
fixup the refactoring errors in 156246
jdonszelmann Jun 24, 2026
a62fbbb
Rollup merge of #158360 - oli-obk:borrowck-cleanups, r=lcnr
JonathanBrouwer Jun 26, 2026
2588ff5
Rollup merge of #157127 - scottmcm:tweak-layout-of-alternative, r=nikic
JonathanBrouwer Jun 26, 2026
4927087
Rollup merge of #158244 - kantnero:attribute-docs-deprecated-warn, r=…
JonathanBrouwer Jun 26, 2026
13cc1d2
Rollup merge of #158355 - jdonszelmann:fixuop, r=lcnr
JonathanBrouwer Jun 26, 2026
21bf4ef
Rollup merge of #158361 - obeis:move-check-ffi-pure, r=JonathanBrouwer
JonathanBrouwer Jun 26, 2026
7666c90
Rollup merge of #158399 - joboet:netbsd_threadname_truncate, r=jhpratt
JonathanBrouwer Jun 26, 2026
4b9e13c
Rollup merge of #158430 - TaKO8Ki:fix-clone-suggestion-empty-obligati…
JonathanBrouwer Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions compiler/rustc_attr_parsing/src/attributes/link_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ use super::util::parse_single_integer;
use crate::attributes::AttributeSafety;
use crate::attributes::cfg::parse_cfg_entry;
use crate::session_diagnostics::{
AsNeededCompatibility, BundleNeedsStatic, EmptyLinkName, ExportSymbolsNeedsStatic,
ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink, InvalidLinkModifier,
InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple, LinkOrdinalOutOfRange,
LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection, RawDylibOnlyWindows,
WholeArchiveNeedsStatic,
AsNeededCompatibility, BothFfiConstAndPure, BundleNeedsStatic, EmptyLinkName,
ExportSymbolsNeedsStatic, ImportNameTypeRaw, ImportNameTypeX86, IncompatibleWasmLink,
InvalidLinkModifier, InvalidMachoSection, InvalidMachoSectionReason, LinkFrameworkApple,
LinkOrdinalOutOfRange, LinkRequiresName, MultipleModifiers, NullOnLinkName, NullOnLinkSection,
RawDylibOnlyWindows, WholeArchiveNeedsStatic,
};

pub(crate) struct LinkNameParser;
Expand Down Expand Up @@ -575,6 +575,13 @@ impl NoArgsAttributeParser for FfiPureParser {
AllowedTargets::AllowList(&[Allow(Target::ForeignFn)]);
const STABILITY: AttributeStability = unstable!(ffi_pure);
const CREATE: fn(Span) -> AttributeKind = AttributeKind::FfiPure;

fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) {
// `#[ffi_const]` functions cannot be `#[ffi_pure]`.
if cx.all_attrs.iter().any(|a| a.word_is(sym::ffi_const)) {
cx.emit_err(BothFfiConstAndPure { attr_span });
}
}
}

pub(crate) struct RustcStdInternalSymbolParser;
Expand Down
26 changes: 24 additions & 2 deletions compiler/rustc_attr_parsing/src/attributes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ pub(crate) trait SingleAttributeParser: 'static {

/// Converts a single syntactical attribute to a single semantic attribute, or [`AttributeKind`]
fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<AttributeKind>;

/// Optional cross-attribute validation, run once during finalization after all
/// attributes on the item have been parsed. Unlike [`convert`](Self::convert), this
/// has access to the sibling attributes via [`FinalizeContext::all_attrs`], so it can
/// reject incompatible combinations. `attr_span` is the span of this attribute.
///
/// Defaults to a no-op.
fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {}
}

/// Use in combination with [`SingleAttributeParser`].
Expand Down Expand Up @@ -177,8 +185,10 @@ impl<T: SingleAttributeParser> AttributeParser for Single<T> {
const ALLOWED_TARGETS: AllowedTargets<'_> = T::ALLOWED_TARGETS;
const SAFETY: AttributeSafety = T::SAFETY;

fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
Some(self.1?.0)
fn finalize(self, cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
let (kind, span) = self.1?;
T::finalize_check(span, cx);
Some(kind)
}
}

Expand Down Expand Up @@ -258,6 +268,14 @@ pub(crate) trait NoArgsAttributeParser: 'static {

/// Create the [`AttributeKind`] given attribute's [`Span`].
const CREATE: fn(Span) -> AttributeKind;

/// Optional cross-attribute validation, run once during finalization after all
/// attributes on the item have been parsed. Has access to the sibling attributes via
/// [`FinalizeContext::all_attrs`], so it can reject incompatible combinations.
/// `attr_span` is the span of this attribute.
///
/// Defaults to a no-op.
fn finalize_check(_attr_span: Span, _cx: &FinalizeContext<'_, '_>) {}
}

pub(crate) struct WithoutArgs<T: NoArgsAttributeParser>(PhantomData<T>);
Expand All @@ -280,6 +298,10 @@ impl<T: NoArgsAttributeParser> SingleAttributeParser for WithoutArgs<T> {
let _ = cx.expect_no_args(args);
Some(T::CREATE(cx.attr_span))
}

fn finalize_check(attr_span: Span, cx: &FinalizeContext<'_, '_>) {
T::finalize_check(attr_span, cx)
}
}

type ConvertFn<E> = fn(ThinVec<E>, Span) -> AttributeKind;
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ use rustc_target::spec::TargetTuple;
use crate::AttributeTemplate;
use crate::context::Suggestion;

#[derive(Diagnostic)]
#[diag("`#[ffi_const]` function cannot be `#[ffi_pure]`", code = E0757)]
pub(crate) struct BothFfiConstAndPure {
#[primary_span]
pub attr_span: Span,
}

#[derive(Diagnostic)]
#[diag("{$attr_str} attribute cannot have empty value")]
pub(crate) struct DocAliasEmpty<'a> {
Expand Down
22 changes: 13 additions & 9 deletions compiler/rustc_borrowck/src/diagnostics/conflict_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1461,15 +1461,19 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
let cause = ObligationCause::misc(expr.span, self.mir_def_id());
ocx.register_bound(cause, self.infcx.param_env, ty, clone_trait);
let errors = ocx.evaluate_obligations_error_on_ambiguity();
if errors.iter().all(|error| {
match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) {
Some(clause) => match clause.self_ty().skip_binder().kind() {
ty::Adt(def, _) => def.did().is_local() && clause.def_id() == clone_trait,
_ => false,
},
None => false,
}
}) {
if !errors.is_empty()
&& errors.iter().all(|error| {
match error.obligation.predicate.as_clause().and_then(|c| c.as_trait_clause()) {
Some(clause) => match clause.self_ty().skip_binder().kind() {
ty::Adt(def, _) => {
def.did().is_local() && clause.def_id() == clone_trait
}
_ => false,
},
None => false,
}
})
{
let mut type_spans = vec![];
let mut types = FxIndexSet::default();
for clause in errors
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ impl<'tcx> MirBorrowckCtxt<'_, '_, 'tcx> {
Some(Cause::LiveVar(..) | Cause::DropVar(..)) | None => {
// 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.
if let Some(region) = self.to_error_region_vid(borrow_region_vid) {
if let Some(region) = self.regioncx.to_error_region_vid(borrow_region_vid) {
let (category, from_closure, span, region_name, path) =
self.free_region_constraint_info(borrow_region_vid, region);
if let Some(region_name) = region_name {
Expand Down
61 changes: 32 additions & 29 deletions compiler/rustc_borrowck/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,42 @@ impl<'infcx, 'tcx> BorrowckDiagnosticsBuffer<'infcx, 'tcx> {
pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) {
self.buffered_diags.push(BufferedDiag::NonError(diag));
}
pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) {
self.buffered_diags.push(BufferedDiag::Error(diag));
}

pub(crate) fn emit_errors(&mut self) -> Option<ErrorGuaranteed> {
let mut res = None;

// Buffer any move errors that we collected and de-duplicated.
for (_, (_, diag)) in std::mem::take(&mut self.buffered_move_errors) {
// We have already set tainted for this error, so just buffer it.
self.buffer_error(diag);
}
for (_, (mut diag, count)) in std::mem::take(&mut self.buffered_mut_errors) {
if count > 10 {
diag.note(format!("...and {} other attempted mutable borrows", count - 10));
}
self.buffer_error(diag);
}

if !self.buffered_diags.is_empty() {
self.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span());
for buffered_diag in self.buffered_diags.drain(..) {
match buffered_diag {
BufferedDiag::Error(diag) => res = Some(diag.emit()),
BufferedDiag::NonError(diag) => diag.emit(),
}
}
}

res
}
}

impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
pub(crate) fn buffer_error(&mut self, diag: Diag<'infcx>) {
self.diags_buffer.buffered_diags.push(BufferedDiag::Error(diag));
self.diags_buffer.buffer_error(diag);
}

pub(crate) fn buffer_non_error(&mut self, diag: Diag<'infcx, ()>) {
Expand Down Expand Up @@ -152,34 +183,6 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
self.diags_buffer.buffered_mut_errors.insert(span, (diag, count));
}

pub(crate) fn emit_errors(&mut self) -> Option<ErrorGuaranteed> {
let mut res = self.infcx.tainted_by_errors();

// Buffer any move errors that we collected and de-duplicated.
for (_, (_, diag)) in std::mem::take(&mut self.diags_buffer.buffered_move_errors) {
// We have already set tainted for this error, so just buffer it.
self.buffer_error(diag);
}
for (_, (mut diag, count)) in std::mem::take(&mut self.diags_buffer.buffered_mut_errors) {
if count > 10 {
diag.note(format!("...and {} other attempted mutable borrows", count - 10));
}
self.buffer_error(diag);
}

if !self.diags_buffer.buffered_diags.is_empty() {
self.diags_buffer.buffered_diags.sort_by_key(|buffered_diag| buffered_diag.sort_span());
for buffered_diag in self.diags_buffer.buffered_diags.drain(..) {
match buffered_diag {
BufferedDiag::Error(diag) => res = Some(diag.emit()),
BufferedDiag::NonError(diag) => diag.emit(),
}
}
}

res
}

pub(crate) fn has_buffered_diags(&self) -> bool {
self.diags_buffer.buffered_diags.is_empty()
}
Expand Down
8 changes: 2 additions & 6 deletions compiler/rustc_borrowck/src/diagnostics/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}

let infcx = self.infcx;
let mut guar = None;
let mut last_unexpected_hidden_region: Option<(Span, Ty<'_>, ty::OpaqueTypeKey<'tcx>)> =
None;
for error in errors {
guar = Some(match error {
match error {
DeferredOpaqueTypeError::InvalidOpaqueTypeArgs(err) => err.report(infcx),
DeferredOpaqueTypeError::LifetimeMismatchOpaqueParam(err) => {
infcx.dcx().emit_err(err)
Expand Down Expand Up @@ -82,11 +81,8 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
)
),
),
});
};
}
let guar = guar.unwrap();
self.root_cx.set_tainted_by_errors(guar);
self.infcx.set_tainted_by_errors(guar);
}

/// Try to note when an opaque is involved in a borrowck error and that
Expand Down
40 changes: 26 additions & 14 deletions compiler/rustc_borrowck/src/diagnostics/region_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use rustc_trait_selection::traits::{Obligation, ObligationCtxt};
use tracing::{debug, instrument, trace};

use super::{LIMITATION_NOTE, OutlivesSuggestionBuilder, RegionName, RegionNameSource};
use crate::consumers::RegionInferenceContext;
use crate::nll::ConstraintDescription;
use crate::region_infer::{BlameConstraint, TypeTest};
use crate::session_diagnostics::{
Expand Down Expand Up @@ -134,28 +135,28 @@ pub(crate) struct ErrorConstraintInfo<'tcx> {
pub(super) span: Span,
}

impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
impl<'tcx> RegionInferenceContext<'tcx> {
/// Converts a region inference variable into a `ty::Region` that
/// we can use for error reporting. If `r` is universally bound,
/// then we use the name that we have on record for it. If `r` is
/// existentially bound, then we check its inferred value and try
/// to find a good name from that. Returns `None` if we can't find
/// one (e.g., this is just some random part of the CFG).
pub(super) fn to_error_region(&self, r: RegionVid) -> Option<ty::Region<'tcx>> {
self.to_error_region_vid(r).and_then(|r| self.regioncx.region_definition(r).external_name)
self.to_error_region_vid(r).and_then(|r| self.region_definition(r).external_name)
}

/// Returns the `RegionVid` corresponding to the region returned by
/// `to_error_region`.
pub(super) fn to_error_region_vid(&self, r: RegionVid) -> Option<RegionVid> {
if self.regioncx.universal_regions().is_universal_region(r) {
if self.universal_regions().is_universal_region(r) {
Some(r)
} else {
// We just want something nameable, even if it's not
// actually an upper bound.
let upper_bound = self.regioncx.approx_universal_upper_bound(r);
let upper_bound = self.approx_universal_upper_bound(r);

if self.regioncx.upper_bound_in_region_scc(r, upper_bound) {
if self.upper_bound_in_region_scc(r, upper_bound) {
self.to_error_region_vid(upper_bound)
} else {
None
Expand All @@ -179,14 +180,16 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
if let Some(r) = self.to_error_region(fr)
&& let ty::ReLateParam(late_param) = r.kind()
&& let ty::LateParamRegionKind::ClosureEnv = late_param.kind
&& let DefiningTy::Closure(_, args) = self.regioncx.universal_regions().defining_ty
&& let DefiningTy::Closure(_, args) = self.universal_regions().defining_ty
{
return args.as_closure().kind() == ty::ClosureKind::FnMut;
}

false
}
}

impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// For generic associated types (GATs) which implied 'static requirement
// from higher-ranked trait bounds (HRTB). Try to locate span of the trait
// and the span which bounded to the trait for adding 'static lifetime suggestion
Expand Down Expand Up @@ -309,12 +312,12 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
RegionErrorKind::TypeTestError { type_test } => {
// Try to convert the lower-bound region into something named we can print for
// the user.
let lower_bound_region = self.to_error_region(type_test.lower_bound);
let lower_bound_region = self.regioncx.to_error_region(type_test.lower_bound);

let type_test_span = type_test.span;

if let Some(lower_bound_region) = lower_bound_region {
let generic_ty = self.name_regions(
let generic_ty = self.regioncx.name_regions(
self.infcx.tcx,
type_test.generic_kind.to_ty(self.infcx.tcx),
);
Expand All @@ -324,7 +327,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
self.body.source.def_id().expect_local(),
type_test_span,
Some(origin),
self.name_regions(self.infcx.tcx, type_test.generic_kind),
self.regioncx.name_regions(self.infcx.tcx, type_test.generic_kind),
lower_bound_region,
));
} else {
Expand Down Expand Up @@ -450,7 +453,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
debug!("report_region_error: category={:?} {:?} {:?}", category, cause, variance_info);

// Check if we can use one of the "nice region errors".
if let (Some(f), Some(o)) = (self.to_error_region(fr), self.to_error_region(outlived_fr)) {
if let (Some(f), Some(o)) =
(self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr))
{
let infer_err = self.infcx.err_ctxt();
let nice =
NiceRegionError::new_from_span(&infer_err, self.mir_def_id(), cause.span, o, f);
Expand Down Expand Up @@ -481,7 +486,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
d.note("meoow :c");
d
}
(ConstraintCategory::Return(kind), true, false) if self.is_closure_fn_mut(fr) => {
(ConstraintCategory::Return(kind), true, false)
if self.regioncx.is_closure_fn_mut(fr) =>
{
self.report_fnmut_error(&errci, kind)
}
(ConstraintCategory::Assignment, true, false)
Expand Down Expand Up @@ -736,7 +743,10 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
// Only show an extra note if we can find an 'error region' for both of the region
// variables. This avoids showing a noisy note that just mentions 'synthetic' regions
// that don't help the user understand the error.
match (self.to_error_region(errci.fr), self.to_error_region(errci.outlived_fr)) {
match (
self.regioncx.to_error_region(errci.fr),
self.regioncx.to_error_region(errci.outlived_fr),
) {
(Some(f), Some(o)) => {
self.maybe_suggest_constrain_dyn_trait_impl(&mut diag, f, o, category);

Expand Down Expand Up @@ -842,7 +852,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
outlived_fr: RegionVid,
) {
if let (Some(f), Some(outlived_f)) =
(self.to_error_region(fr), self.to_error_region(outlived_fr))
(self.regioncx.to_error_region(fr), self.regioncx.to_error_region(outlived_fr))
{
if outlived_f.kind() != ty::ReStatic {
return;
Expand Down Expand Up @@ -1013,7 +1023,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
}

fn suggest_adding_lifetime_params(&self, diag: &mut Diag<'_>, sub: RegionVid, sup: RegionVid) {
let (Some(sub), Some(sup)) = (self.to_error_region(sub), self.to_error_region(sup)) else {
let (Some(sub), Some(sup)) =
(self.regioncx.to_error_region(sub), self.regioncx.to_error_region(sup))
else {
return;
};

Expand Down
Loading
Loading