Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
cb37077
Fix 'assign to data in an index of' collection suggestions
GTimothy Dec 16, 2025
f2bf6a8
rebless the ui tests
GTimothy Feb 14, 2026
9de80d4
add index-mut test case where mut index access to a map produces E027…
GTimothy Apr 30, 2026
5f1e962
fix: wrong assumption could cause panic
GTimothy May 2, 2026
d4997ca
Fix `doc_cfg` feature on reexports
GuillaumeGomez May 2, 2026
6f644d8
Better use of conditions in `propagate_doc_cfg`
GuillaumeGomez May 3, 2026
ba27564
Keep the original import `DefId` when inlining items through multiple…
GuillaumeGomez May 3, 2026
df8a13e
Hand-implement `impl Debug for NumBuffer` to avoid constraining `T` o…
joshtriplett May 3, 2026
8290841
Stop using `rustc_scalar_layout` attr in gvn test
oli-obk Apr 16, 2026
25d0328
Stop using `rustc_scalar_layout` attr in jump threading test
oli-obk Apr 16, 2026
df112cf
Stop using `rustc_scalar_layout` in enum test
oli-obk Apr 16, 2026
e397ac2
Remove mir-opt mention of rustc_scalar_layout attr
oli-obk Apr 16, 2026
8882bf0
Remove rustc_layout_scalar_valid_range attr from debuginfo
oli-obk Apr 17, 2026
11d88ee
Rip out rustc_layout_scalar_valid_range_* attribute support
oli-obk Feb 13, 2026
bc6d9b1
Rollup merge of #156073 - GuillaumeGomez:doc-cfg-reexports, r=Urgau
JonathanBrouwer May 3, 2026
11610db
Rollup merge of #152216 - GTimothy:map-diagnostics-fix, r=JonathanBro…
JonathanBrouwer May 3, 2026
081e7c4
Rollup merge of #155433 - oli-obk:bye-bye-long-attribute, r=RalfJung,…
JonathanBrouwer May 3, 2026
0d98c12
Rollup merge of #156098 - joshtriplett:num-buffer-debug, r=GuillaumeG…
JonathanBrouwer May 3, 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
51 changes: 1 addition & 50 deletions compiler/rustc_abi/src/layout.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::collections::BTreeSet;
use std::fmt::{self, Write};
use std::ops::{Bound, Deref};
use std::ops::Deref;
use std::{cmp, iter};

use rustc_hashes::Hash64;
Expand Down Expand Up @@ -348,7 +348,6 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
variants: &IndexSlice<VariantIdx, IndexVec<FieldIdx, F>>,
is_enum: bool,
is_special_no_niche: bool,
scalar_valid_range: (Bound<u128>, Bound<u128>),
discr_range_of_repr: impl Fn(i128, i128) -> (Integer, bool),
discriminants: impl Iterator<Item = (VariantIdx, i128)>,
always_sized: bool,
Expand Down Expand Up @@ -380,7 +379,6 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
variants,
is_enum,
is_special_no_niche,
scalar_valid_range,
always_sized,
present_first,
)
Expand Down Expand Up @@ -530,7 +528,6 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
variants: &IndexSlice<VariantIdx, IndexVec<FieldIdx, F>>,
is_enum: bool,
is_special_no_niche: bool,
scalar_valid_range: (Bound<u128>, Bound<u128>),
always_sized: bool,
present_first: VariantIdx,
) -> LayoutCalculatorResult<FieldIdx, VariantIdx, F> {
Expand Down Expand Up @@ -570,52 +567,6 @@ impl<Cx: HasDataLayout> LayoutCalculator<Cx> {
return Ok(st);
}

let (start, end) = scalar_valid_range;
match st.backend_repr {
BackendRepr::Scalar(ref mut scalar) | BackendRepr::ScalarPair(ref mut scalar, _) => {
// Enlarging validity ranges would result in missed
// optimizations, *not* wrongly assuming the inner
// value is valid. e.g. unions already enlarge validity ranges,
// because the values may be uninitialized.
//
// Because of that we only check that the start and end
// of the range is representable with this scalar type.

let max_value = scalar.size(dl).unsigned_int_max();
if let Bound::Included(start) = start {
// FIXME(eddyb) this might be incorrect - it doesn't
// account for wrap-around (end < start) ranges.
assert!(start <= max_value, "{start} > {max_value}");
scalar.valid_range_mut().start = start;
}
if let Bound::Included(end) = end {
// FIXME(eddyb) this might be incorrect - it doesn't
// account for wrap-around (end < start) ranges.
assert!(end <= max_value, "{end} > {max_value}");
scalar.valid_range_mut().end = end;
}

// Update `largest_niche` if we have introduced a larger niche.
let niche = Niche::from_scalar(dl, Size::ZERO, *scalar);
if let Some(niche) = niche {
match st.largest_niche {
Some(largest_niche) => {
// Replace the existing niche even if they're equal,
// because this one is at a lower offset.
if largest_niche.available(dl) <= niche.available(dl) {
st.largest_niche = Some(niche);
}
}
None => st.largest_niche = Some(niche),
}
}
}
_ => assert!(
start == Bound::Unbounded && end == Bound::Unbounded,
"nonscalar layout for layout_scalar_valid_range type: {st:#?}",
),
}

Ok(st)
}

Expand Down
26 changes: 0 additions & 26 deletions compiler/rustc_attr_parsing/src/attributes/rustc_internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,6 @@ impl NoArgsAttributeParser for RustcNoImplicitAutorefsParser {
const CREATE: fn(Span) -> AttributeKind = |_| AttributeKind::RustcNoImplicitAutorefs;
}

pub(crate) struct RustcLayoutScalarValidRangeStartParser;

impl SingleAttributeParser for RustcLayoutScalarValidRangeStartParser {
const PATH: &[Symbol] = &[sym::rustc_layout_scalar_valid_range_start];
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["start"]);

fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeStart(Box::new(n), cx.attr_span))
}
}

pub(crate) struct RustcLayoutScalarValidRangeEndParser;

impl SingleAttributeParser for RustcLayoutScalarValidRangeEndParser {
const PATH: &[Symbol] = &[sym::rustc_layout_scalar_valid_range_end];
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(&[Allow(Target::Struct)]);
const TEMPLATE: AttributeTemplate = template!(List: &["end"]);

fn convert(cx: &mut AcceptContext<'_, '_>, args: &ArgParser) -> Option<AttributeKind> {
parse_single_integer(cx, args)
.map(|n| AttributeKind::RustcLayoutScalarValidRangeEnd(Box::new(n), cx.attr_span))
}
}

pub(crate) struct RustcLegacyConstGenericsParser;

impl SingleAttributeParser for RustcLegacyConstGenericsParser {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/attributes/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pub fn is_builtin_attr(attr: &ast::Attribute) -> bool {
/// Parse a single integer.
///
/// Used by attributes that take a single integer as argument, such as
/// `#[link_ordinal]` and `#[rustc_layout_scalar_valid_range_start]`.
/// `#[link_ordinal]`.
/// `cx` is the context given to the attribute.
/// `args` is the parser for the attribute arguments.
pub(crate) fn parse_single_integer(
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,6 @@ attribute_parsers!(
Single<RustcDumpSymbolNameParser>,
Single<RustcForceInlineParser>,
Single<RustcIfThisChangedParser>,
Single<RustcLayoutScalarValidRangeEndParser>,
Single<RustcLayoutScalarValidRangeStartParser>,
Single<RustcLegacyConstGenericsParser>,
Single<RustcLintOptDenyFieldAccessParser>,
Single<RustcMacroTransparencyParser>,
Expand Down
173 changes: 141 additions & 32 deletions compiler/rustc_borrowck/src/diagnostics/mutability_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rustc_abi::FieldIdx;
use rustc_errors::{Applicability, Diag};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, BindingMode, ByRef, Node};
use rustc_hir::{self as hir, BindingMode, ByRef, Expr, Node};
use rustc_middle::bug;
use rustc_middle::hir::place::PlaceBase;
use rustc_middle::mir::visit::PlaceContext;
Expand Down Expand Up @@ -669,7 +669,9 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
err: &'a mut Diag<'infcx>,
ty: Ty<'tcx>,
suggested: bool,
infcx: &'a rustc_infer::infer::InferCtxt<'tcx>,
}

impl<'a, 'infcx, 'tcx> Visitor<'tcx> for SuggestIndexOperatorAlternativeVisitor<'a, 'infcx, 'tcx> {
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt<'tcx>) {
hir::intravisit::walk_stmt(self, stmt);
Expand All @@ -680,60 +682,166 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
return;
}
};

// Because of TypeChecking and indexing, we know: index is &Q
// with K: Eq + Hash + Borrow<Q>,
// with Q: Eq + Hash + ?Sized,
//
// which fulfill the requirements of `get_mut`. If Q=K or Q=&{n}K, the requirements
// of `entry` and `insert` are fulfilled too after dereferencing. If K is not
// copy, a subsequent `clone` call may be needed.

/// Taken straight from https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/fn.peel_hir_ty_refs.html
/// Adapted to mid using https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html#method.peel_refs
/// Simplified to counting only
/// Peels off all references on the type. Returns the number of references
/// removed.
fn count_ty_refs<'tcx>(mut ty: Ty<'tcx>) -> usize {
let mut count = 0;
while let ty::Ref(_, inner_ty, _) = ty.kind() {
ty = *inner_ty;
count += 1;
}
count
}

/// Try to strip `n` `&` reference from an expression.
/// If the expression does not have enough leading `&`, return an Error
/// containing a count of the successfully stripped ones and the stripped
/// expression.
fn strip_n_refs<'a, 'b>(
mut expr: &'a Expr<'b>,
n: usize,
) -> Result<&'a Expr<'b>, (usize, &'a Expr<'b>)> {
for count in 0..n {
match expr {
Expr {
kind: ExprKind::AddrOf(hir::BorrowKind::Ref, _, inner),
..
} => expr = inner,
_ => return Err((count, expr)),
}
}
Ok(expr)
}

// we know ty is a map, with a key type at walk distance 2.
let key_ty = self.ty.walk().nth(1).unwrap().expect_ty();

if let hir::ExprKind::Assign(place, rv, _sp) = expr.kind
&& let hir::ExprKind::Index(val, index, _) = place.kind
&& (expr.span == self.assign_span || place.span == self.assign_span)
{
// val[index] = rv;
// ---------- place
self.err.multipart_suggestions(
format!(
"use `.insert()` to insert a value into a `{}`, `.get_mut()` \
to modify it, or the entry API for more flexibility",
self.ty,
),
vec![
let index_ty =
self.infcx.tcx.typeck(val.hir_id.owner.def_id).expr_ty(index);

let (borrowed_prefix, borrowed_index);

// only suggest `insert` and `entry` if index is of type K or &{n}K or *{n}K (when there is a Borrow impl for this case).
// We use `peel_refs` because borrow lifetimes may differ in both index and
// key. I.e, if they are of the same base type:
if index_ty.peel_refs() == key_ty.peel_refs() {
let (index_refs, key_refs) =
(count_ty_refs(index_ty), count_ty_refs(key_ty));

let (deref_prefix, deref_index) = if index_refs >= key_refs {
// index is &{n}K
strip_n_refs(index, index_refs - key_refs)
.map(|val| ("".to_string(), val))
.unwrap_or_else(|(depth, val)| {
(
if key_refs == 0 {
"*".repeat(
(index_refs-key_refs).checked_sub(depth).expect("return depth from strip_n_refs should be smaller than the input")
)
} else {
String::new() //if key K is a ref, autoderef finish this for us.
},
val,
)
})
} else {
// in this case the minimal ref addition works for all subcases
("&".repeat(key_refs - index_refs), index)
};

self.err.multipart_suggestion(
format!("use `.insert()` to insert a value into a `{}`", self.ty),
vec![
// val.insert(index, rv);
// val.insert({deref_prefix}{deref_index}, rv);
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
".insert(".to_string(),
val.span.shrink_to_hi().with_hi(deref_index.span.lo()),
format!(".insert({deref_prefix}"),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
deref_index.span.shrink_to_hi().with_hi(rv.span.lo()),
", ".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);
self.err.multipart_suggestion(
format!(
"use the entry API to modify a `{}` for more flexibility",
self.ty
),
vec![
// if let Some(v) = val.get_mut(index) { *v = rv; }
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
".get_mut(".to_string(),
),
(
index.span.shrink_to_hi().with_hi(place.span.hi()),
") { *val".to_string(),
),
(rv.span.shrink_to_hi(), "; }".to_string()),
],
vec![
// let x = val.entry(index).or_insert(rv);
// let x = val.entry({deref_prefix}{deref_index}).insert_entry(rv);
(val.span.shrink_to_lo(), "let val = ".to_string()),
(
val.span.shrink_to_hi().with_hi(index.span.lo()),
".entry(".to_string(),
val.span.shrink_to_hi().with_hi(deref_index.span.lo()),
format!(".entry({deref_prefix}"),
),
(
index.span.shrink_to_hi().with_hi(rv.span.lo()),
").or_insert(".to_string(),
deref_index.span.shrink_to_hi().with_hi(rv.span.lo()),
").insert_entry(".to_string(),
),
(rv.span.shrink_to_hi(), ")".to_string()),
],
Applicability::MaybeIncorrect,
);

// we can make the next suggestions nicer by stripping as many leading `&` as
// we can, autoderef will do the rest
(borrowed_prefix, borrowed_index) = (
String::new(),
if index_refs > key_refs {
strip_n_refs(index, index_refs - key_refs - 1)
.unwrap_or_else(|(_depth, val)| val)
// even if we tried to strip more, we can stop there thanks to autoderef
} else {
// when the diff is negative or zero, we already are in the index=&Q case.
index
},
);
} else {
(borrowed_prefix, borrowed_index) = (String::new(), index)
}
// in all cases, suggest get_mut because K:Borrow<K> or Q:Borrow<K> as a
// requirement of indexing.
self.err.multipart_suggestion(
format!(
"use `.get_mut()` to modify an existing key in a `{}`",
self.ty,
),
vec![
// if let Some(v) = val.get_mut({borrowed_prefix}{borrowed_index}) { *v = rv; }
(val.span.shrink_to_lo(), "if let Some(val) = ".to_string()),
(
val.span.shrink_to_hi().with_hi(borrowed_index.span.lo()),
format!(".get_mut({borrowed_prefix}"),
),
(
borrowed_index.span.shrink_to_hi().with_hi(place.span.hi()),
") { *val".to_string(),
),
(rv.span.shrink_to_hi(), "; }".to_string()),
],
Applicability::MachineApplicable,
Applicability::MaybeIncorrect,
);

self.suggested = true;
} else if let hir::ExprKind::MethodCall(_path, receiver, _, sp) = expr.kind
&& let hir::ExprKind::Index(val, index, _) = receiver.kind
Expand Down Expand Up @@ -769,6 +877,7 @@ impl<'infcx, 'tcx> MirBorrowckCtxt<'_, 'infcx, 'tcx> {
err,
ty,
suggested: false,
infcx: self.infcx,
};
v.visit_body(&body);
if !v.suggested {
Expand Down
Loading
Loading