From bdbec70326fe832056a95428ef170f2950f28f31 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 03:55:40 +0000 Subject: [PATCH 1/3] Update toolchain to nightly-2026-03-08 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adapts thrust to breaking MIR and API changes introduced between nightly-2025-09-08 and nightly-2026-03-08: - rust-toolchain.toml: bump channel to nightly-2026-03-08 - src/main.rs: update for new rustc query/provider API shape (mir_borrowck, StripTokens, AllowConstBlockItems, ExitCode return) - src/analyze.rs: fix mir_borrowck_skip_formula_fn signature for new ErrorGuaranteed / FxIndexMap types - src/analyze/basic_block.rs: handle ReifyFnPointer(_) wildcard and new Operand::RuntimeChecks variant - src/analyze/local_def.rs: handle two new MIR elaboration patterns that replace CopyForDeref in 2026-03-08: 1. Two-stage box deref: `_nonnull = copy box.Field(Unique).Field(NonNull)` followed by `_ptr = Transmute(_nonnull)` — fixes list_length_const etc. 2. Closure &mut T capture: `_alias = copy (*closure).field` with &mut T type — fixes closure_mut / closure_mut_0 false negatives - src/refine/env.rs: fix large_enum_variant (box PlaceType), path_type deref, and locate_place loop style for new clippy lints - tests/ui/fail/{just_rec,split,fn_poly_param_order,adt_discr, extern_spec_take,annot_formula_fn,annot_preds_trait, annot_preds_trait_multi,iterators/range}.rs: add //@no-rustfix to suppress ui_test rustfix checks that now correctly detect Unsat https://claude.ai/code/session_01URrtbSTUKNrVKLK2MNLBFc --- rust-toolchain.toml | 2 +- src/analyze.rs | 17 +++- src/analyze/basic_block.rs | 5 +- src/analyze/local_def.rs | 121 ++++++++++++++++++----- src/main.rs | 15 +-- src/refine/env.rs | 5 +- src/rty.rs | 2 +- tests/ui/fail/adt_discr.rs | 1 + tests/ui/fail/annot_formula_fn.rs | 1 + tests/ui/fail/annot_preds.rs | 1 + tests/ui/fail/annot_preds_trait.rs | 1 + tests/ui/fail/annot_preds_trait_multi.rs | 1 + tests/ui/fail/extern_spec_take.rs | 1 + tests/ui/fail/fn_poly_param_order.rs | 1 + tests/ui/fail/iterators/range.rs | 1 + tests/ui/fail/just_rec.rs | 1 + tests/ui/fail/split.rs | 1 + 17 files changed, 133 insertions(+), 44 deletions(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 56e7f16a..d29b2337 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2025-09-08" +channel = "nightly-2026-03-08" components = [ "rustc-dev", "rust-src", "llvm-tools-preview", "rust-analyzer" ] diff --git a/src/analyze.rs b/src/analyze.rs index 9339b1da..1061135c 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -34,10 +34,16 @@ mod local_def; // TODO: organize structure and remove cross dependency between refine pub use did_cache::DefIdCache; -pub fn mir_borrowck_skip_formula_fn( - tcx: rustc_middle::ty::TyCtxt<'_>, +pub fn mir_borrowck_skip_formula_fn<'tcx>( + tcx: rustc_middle::ty::TyCtxt<'tcx>, local_def_id: rustc_span::def_id::LocalDefId, -) -> rustc_middle::query::queries::mir_borrowck::ProvidedValue<'_> { +) -> Result< + &'tcx rustc_data_structures::fx::FxIndexMap< + rustc_span::def_id::LocalDefId, + rustc_middle::ty::DefinitionSiteHiddenType<'tcx>, + >, + rustc_span::ErrorGuaranteed, +> { // TODO: unify impl with local_def::Analyzer // if the def is closure defined in formula_fn let root_def_id = tcx.typeck_root_def_id(local_def_id.to_def_id()); @@ -52,8 +58,9 @@ pub fn mir_borrowck_skip_formula_fn( if is_annotated_as_formula_fn { tracing::debug!(?local_def_id, "skipping borrow check for formula fn"); - let dummy_result = rustc_middle::mir::ConcreteOpaqueTypes(Default::default()); - return Ok(tcx.arena.alloc(dummy_result)); + return Ok(tcx + .arena + .alloc(rustc_data_structures::fx::FxIndexMap::default())); } (rustc_interface::DEFAULT_QUERY_PROVIDERS diff --git a/src/analyze/basic_block.rs b/src/analyze/basic_block.rs index eb2e7cb9..d5d780b8 100644 --- a/src/analyze/basic_block.rs +++ b/src/analyze/basic_block.rs @@ -354,6 +354,9 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { let ty = match &operand { Operand::Copy(place) | Operand::Move(place) => self.env.place_type(*place), Operand::Constant(operand) => self.const_ty(&operand.const_), + Operand::RuntimeChecks(_) => { + PlaceTypeBuilder::default().build(rty::Type::bool(), chc::Term::bool(false)) + } }; tracing::debug!(operand = ?operand, ty = %ty.display(), "operand_type"); ty @@ -485,7 +488,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { } Rvalue::Cast( mir::CastKind::PointerCoercion( - mir_ty::adjustment::PointerCoercion::ReifyFnPointer, + mir_ty::adjustment::PointerCoercion::ReifyFnPointer(_), _, ), operand, diff --git a/src/analyze/local_def.rs b/src/analyze/local_def.rs index 1b05008b..188c8ef2 100644 --- a/src/analyze/local_def.rs +++ b/src/analyze/local_def.rs @@ -294,7 +294,7 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { .tcx .opt_associated_item(self.local_def_id.to_def_id())?; let trait_item_id = impl_item_assoc - .trait_item_def_id + .trait_item_def_id() .and_then(|id| id.as_local())?; if trait_item_id == self.local_def_id { @@ -311,11 +311,14 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { return None; } - let trait_ref = self.tcx.impl_trait_ref(impl_did)?.instantiate_identity(); + let trait_ref = self + .tcx + .impl_opt_trait_ref(impl_did)? + .instantiate_identity(); let trait_item_did = self .tcx .associated_item(self.local_def_id.to_def_id()) - .trait_item_def_id + .trait_item_def_id() .unwrap(); self.ctx.def_ty_with_args(trait_item_did, trait_ref.args) } @@ -526,6 +529,42 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { return Some((lhs_local, *place)); } + // nightly-2026+: CopyForDeref was replaced by plain Use(Copy(place)) for &mut T. + // Detect copies of &mut T reached through struct-field projections — these are deref + // aliases created so the result can be immediately dereffed (e.g. closure captures). + // We require a non-empty projection ending with a Field to avoid matching plain + // copies like `return_val = copy param` which are genuine transfers, not deref aliases. + if let mir::Rvalue::Use(mir::Operand::Copy(place)) = &rvalue { + let proj = place.projection.as_slice(); + if let Some(mir::ProjectionElem::Field(..)) = proj.last() { + let place_ty = place.ty(&self.body.local_decls, self.tcx).ty; + if matches!( + place_ty.kind(), + mir_ty::TyKind::Ref(_, _, mir::Mutability::Mut) + ) { + return Some((lhs_local, *place)); + } + } + } + + // nightly-2026+: box deref uses plain `copy (*box_local)` instead of CopyForDeref. + // Box is not Copy, so `lhs = copy ` in optimized MIR must be the + // elaborated box deref pattern (reaching through the raw pointer alias from Transmute). + // The box may be reached through a reference (e.g. `copy (*ref_to_box)`), so we check + // the type of the full place after projections, not just the local's type. + if let mir::Rvalue::Use(mir::Operand::Copy(place)) = &rvalue { + if let Some((rest, [mir::ProjectionElem::Deref])) = + place.projection.as_slice().split_last_chunk::<1>() + { + if rest.is_empty() { + let place_ty = place.ty(&self.body.local_decls, self.tcx).ty; + if place_ty.is_box() { + return Some((lhs_local, *place)); + } + } + } + } + let unique_did = self.ctx.def_ids.unique()?; let nonnull_did = self.ctx.def_ids.nonnull()?; @@ -533,8 +572,38 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { use rustc_abi::FieldIdx; const ZERO_FIELD: FieldIdx = FieldIdx::from_u32(0); + // nightly-2026+: box deref split into two statements. The pre-optimization MIR copies + // the NonNull field directly (instead of going through CopyForDeref/Transmute chain): + // _nonnull = copy box_place.Field(0, Unique).Field(0, NonNull) + // _ptr = Transmute(_nonnull) as *const T + // Detect the first statement and map _nonnull → box_place so the Transmute can be handled. + if let mir::Rvalue::Use(mir::Operand::Copy(place)) = &rvalue { + if let Some((rest, [Field(ZERO_FIELD, ty0), Field(ZERO_FIELD, ty1)])) = + place.projection.as_slice().split_last_chunk::<2>() + { + let rest_place = mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(rest), + }; + let local_ty = rest_place.ty(&self.body.local_decls, self.tcx).ty; + if local_ty.is_box() { + if let Some(inner_ty) = local_ty.boxed_ty() { + if matches!(ty0.kind(), mir_ty::TyKind::Adt(def, args) + if def.did() == unique_did && args.type_at(0) == inner_ty) + && matches!(ty1.kind(), mir_ty::TyKind::Adt(def, args) + if def.did() == nonnull_did && args.type_at(0) == inner_ty) + { + return Some((lhs_local, rest_place)); + } + } + } + } + } + // Box deref pattern: `(_box.0.0 as *const T) Transmute` - // projection = [..., Field(0, Unique), Field(0, NonNull)], transmuted to *const T + // Two variants: + // Old: projection = [..., Field(0, Unique), Field(0, NonNull)], transmuted to *const T + // New (nightly-2026+): operand is a Box local (NonNull was pre-fetched separately) let mir::Rvalue::Cast(mir::CastKind::Transmute, mir::Operand::Copy(place), cast_ty) = &rvalue else { @@ -543,31 +612,33 @@ impl<'tcx, 'ctx> Analyzer<'tcx, 'ctx> { if !matches!(cast_ty.kind(), mir_ty::TyKind::RawPtr(_, mutbl) if mutbl.is_not()) { return None; } - let Some((rest, [Field(ZERO_FIELD, ty0), Field(ZERO_FIELD, ty1)])) = + // Old pattern: last two projections are Field(0,Unique).Field(0,NonNull). + if let Some((rest, [Field(ZERO_FIELD, ty0), Field(ZERO_FIELD, ty1)])) = place.projection.as_slice().split_last_chunk::<2>() - else { - return None; - }; - let rest_place = mir::Place { - local: place.local, - projection: self.tcx.mk_place_elems(rest), - }; - let local_ty = rest_place.ty(&self.body.local_decls, self.tcx).ty; - if !local_ty.is_box() { - return None; - } - let inner_ty = local_ty.boxed_ty()?; - if !matches!(ty0.kind(), mir_ty::TyKind::Adt(def, args) - if def.did() == unique_did && args.type_at(0) == inner_ty) { - return None; + let rest_place = mir::Place { + local: place.local, + projection: self.tcx.mk_place_elems(rest), + }; + let local_ty = rest_place.ty(&self.body.local_decls, self.tcx).ty; + if local_ty.is_box() { + if let Some(inner_ty) = local_ty.boxed_ty() { + if matches!(ty0.kind(), mir_ty::TyKind::Adt(def, args) + if def.did() == unique_did && args.type_at(0) == inner_ty) + && matches!(ty1.kind(), mir_ty::TyKind::Adt(def, args) + if def.did() == nonnull_did && args.type_at(0) == inner_ty) + { + return Some((lhs_local, rest_place)); + } + } + } } - if !matches!(ty1.kind(), mir_ty::TyKind::Adt(def, args) - if def.did() == nonnull_did && args.type_at(0) == inner_ty) - { - return None; + // New pattern (nightly-2026+): operand is already a Box (after NonNull pre-fetch). + let place_ty = place.ty(&self.body.local_decls, self.tcx).ty; + if place_ty.is_box() { + return Some((lhs_local, *place)); } - Some((lhs_local, rest_place)) + None } fn unelaborate_derefs(&mut self) { diff --git a/src/main.rs b/src/main.rs index 960ebc1b..a6977abc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ impl Callbacks for CompilerCalls { attrs.push("register_tool(thrust)".to_owned()); config.override_queries = Some(|_sess, providers| { - providers.mir_borrowck = thrust::mir_borrowck_skip_formula_fn; + providers.queries.mir_borrowck = thrust::mir_borrowck_skip_formula_fn; }); } @@ -38,10 +38,14 @@ impl Callbacks for CompilerCalls { &compiler.sess.psess, rustc_span::FileName::Custom("thrust std injected".to_string()), injected.to_owned(), + rustc_parse::lexer::StripTokens::Nothing, ) .unwrap(); while let Some(item) = parser - .parse_item(rustc_parse::parser::ForceCollect::No) + .parse_item( + rustc_parse::parser::ForceCollect::No, + rustc_parse::parser::AllowConstBlockItems::No, + ) .unwrap() { krate.items.push(item); @@ -90,7 +94,7 @@ fn thrust_macros_path() -> Option { None } -pub fn main() { +pub fn main() -> std::process::ExitCode { let mut args = std::env::args().collect::>(); use tracing_subscriber::{filter::EnvFilter, prelude::*}; @@ -112,8 +116,5 @@ pub fn main() { tracing::warn!("could not locate thrust_macros library"); } - let code = rustc_driver::catch_with_exit_code(|| { - rustc_driver::run_compiler(&args, &mut CompilerCalls {}) - }); - std::process::exit(code); + rustc_driver::catch_with_exit_code(|| rustc_driver::run_compiler(&args, &mut CompilerCalls {})) } diff --git a/src/refine/env.rs b/src/refine/env.rs index 536e8383..4081c6d8 100644 --- a/src/refine/env.rs +++ b/src/refine/env.rs @@ -1013,10 +1013,7 @@ where let mut var = place.local.into(); let mut it = place.projection.into_iter(); - loop { - let Some(elem) = it.next() else { - break; - }; + while let Some(elem) = it.next() { var = match (elem, self.flow_binding(var).expect("deref unbound var")) { (PlaceElem::Deref, &FlowBinding::Box(x)) => x.into(), (PlaceElem::Deref, &FlowBinding::Mut(x, _)) => x.into(), diff --git a/src/rty.rs b/src/rty.rs index c9a3249a..af0a7008 100644 --- a/src/rty.rs +++ b/src/rty.rs @@ -228,7 +228,7 @@ impl FunctionType { &mut self.ret, Box::new(RefinedType::unrefined(Type::unit())), ); - self.ret = Box::new(old_ret.map_var(shift)); + *self.ret = old_ret.map_var(shift); removed } } diff --git a/tests/ui/fail/adt_discr.rs b/tests/ui/fail/adt_discr.rs index cd9d8ac3..077959a6 100644 --- a/tests/ui/fail/adt_discr.rs +++ b/tests/ui/fail/adt_discr.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -C debug-assertions=off pub enum X { diff --git a/tests/ui/fail/annot_formula_fn.rs b/tests/ui/fail/annot_formula_fn.rs index e2ae600b..1e9d778c 100644 --- a/tests/ui/fail/annot_formula_fn.rs +++ b/tests/ui/fail/annot_formula_fn.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix #[thrust::formula_fn] fn _thrust_requires_rand_except(x: i64) -> bool { diff --git a/tests/ui/fail/annot_preds.rs b/tests/ui/fail/annot_preds.rs index 725e23ba..3d011da4 100644 --- a/tests/ui/fail/annot_preds.rs +++ b/tests/ui/fail/annot_preds.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -Adead_code -C debug-assertions=off #[thrust::predicate] diff --git a/tests/ui/fail/annot_preds_trait.rs b/tests/ui/fail/annot_preds_trait.rs index d6e9a28f..eb857990 100644 --- a/tests/ui/fail/annot_preds_trait.rs +++ b/tests/ui/fail/annot_preds_trait.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -Adead_code -C debug-assertions=off // A is represented as Tuple in SMT-LIB2 format. diff --git a/tests/ui/fail/annot_preds_trait_multi.rs b/tests/ui/fail/annot_preds_trait_multi.rs index b1156e50..a1b46161 100644 --- a/tests/ui/fail/annot_preds_trait_multi.rs +++ b/tests/ui/fail/annot_preds_trait_multi.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -Adead_code -C debug-assertions=off #[thrust_macros::context] diff --git a/tests/ui/fail/extern_spec_take.rs b/tests/ui/fail/extern_spec_take.rs index 8dc17fff..22c9bacc 100644 --- a/tests/ui/fail/extern_spec_take.rs +++ b/tests/ui/fail/extern_spec_take.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix #[thrust::extern_spec_fn] #[thrust_macros::requires(true)] diff --git a/tests/ui/fail/fn_poly_param_order.rs b/tests/ui/fail/fn_poly_param_order.rs index 93af596d..5a8a2da2 100644 --- a/tests/ui/fail/fn_poly_param_order.rs +++ b/tests/ui/fail/fn_poly_param_order.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix fn select(a: T, b: U, c: V, which: i32) -> T { if which == 0 { diff --git a/tests/ui/fail/iterators/range.rs b/tests/ui/fail/iterators/range.rs index 1af93a70..f7de3553 100644 --- a/tests/ui/fail/iterators/range.rs +++ b/tests/ui/fail/iterators/range.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -C debug-assertions=off //@rustc-env: THRUST_SOLVER_ARGS= diff --git a/tests/ui/fail/just_rec.rs b/tests/ui/fail/just_rec.rs index df795468..426c74af 100644 --- a/tests/ui/fail/just_rec.rs +++ b/tests/ui/fail/just_rec.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -C debug-assertions=off #[thrust::trusted] diff --git a/tests/ui/fail/split.rs b/tests/ui/fail/split.rs index c45751af..148edbe6 100644 --- a/tests/ui/fail/split.rs +++ b/tests/ui/fail/split.rs @@ -1,4 +1,5 @@ //@error-in-other-file: Unsat +//@no-rustfix //@compile-flags: -C debug-assertions=off #[thrust::trusted] From 440b39caf40cc183c84ad81066ae7cbc69a25349 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 10 May 2026 03:56:06 +0000 Subject: [PATCH 2/3] Add *.mir and rust_out to .gitignore Covers loose MIR dump files and compiled debug binaries produced during nightly toolchain debugging sessions. https://claude.ai/code/session_01URrtbSTUKNrVKLK2MNLBFc --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2f7896d1..9abd5824 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ target/ +mir_dump/ +*.mir +rust_out From eddc4c2200df87be6f9e59798da34d24d63e6852 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 23 May 2026 03:30:35 +0000 Subject: [PATCH 3/3] Disable fp.spacer.global for fixed_filter_loop_none pass test nightly-2026-03-08 generates more complex CHC constraints for the nested iterator pattern (FixedFilter wrapping Range), causing Z3's SPACER algorithm to time out. Disable SPACER by clearing solver args (like range.rs already does) so Z3 uses its default FP strategy. https://claude.ai/code/session_01URrtbSTUKNrVKLK2MNLBFc --- tests/ui/pass/iterators/fixed_filter_loop_none.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/ui/pass/iterators/fixed_filter_loop_none.rs b/tests/ui/pass/iterators/fixed_filter_loop_none.rs index 393a1a30..d806f978 100644 --- a/tests/ui/pass/iterators/fixed_filter_loop_none.rs +++ b/tests/ui/pass/iterators/fixed_filter_loop_none.rs @@ -1,5 +1,6 @@ //@check-pass //@compile-flags: -C debug-assertions=off +//@rustc-env: THRUST_SOLVER_ARGS= struct Range { start: i64,