diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index c32e3457d73a1..d488fb8778d0f 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -26,19 +26,21 @@ use rustc_abi::FieldIdx; use rustc_data_structures::frozen::Frozen; use rustc_data_structures::fx::{FxIndexMap, FxIndexSet}; use rustc_data_structures::graph::dominators::Dominators; +use rustc_data_structures::unord::UnordMap; use rustc_hir as hir; use rustc_hir::CRATE_HIR_ID; -use rustc_hir::def_id::LocalDefId; use rustc_index::bit_set::MixedBitSet; use rustc_index::{IndexSlice, IndexVec}; use rustc_infer::infer::outlives::env::RegionBoundPairs; use rustc_infer::infer::{ InferCtxt, NllRegionVariableOrigin, RegionVariableOrigin, TyCtxtInferExt, }; +use rustc_infer::traits::{Obligation, ObligationCauseCode, TraitErrors}; use rustc_middle::mir::*; use rustc_middle::query::Providers; use rustc_middle::ty::{ - self, ParamEnv, RegionVid, Ty, TyCtxt, TypeFoldable, TypeVisitable, TypingMode, fold_regions, + self, ParamEnv, RegionVid, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, + TypeVisitor, TypingMode, Upcast, fold_regions, }; use rustc_middle::{bug, span_bug}; use rustc_mir_dataflow::impls::{EverInitializedPlaces, MaybeUninitializedPlaces}; @@ -48,7 +50,10 @@ use rustc_mir_dataflow::move_paths::{ use rustc_mir_dataflow::points::DenseLocationMap; use rustc_mir_dataflow::{Analysis, EntryStates, Results, ResultsVisitor, visit_results}; use rustc_session::lint::builtin::{TAIL_EXPR_DROP_ORDER, UNUSED_MUT}; +use rustc_span::def_id::LocalDefId; use rustc_span::{ErrorGuaranteed, Span, Symbol}; +use rustc_trait_selection::error_reporting::InferCtxtErrorExt; +use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::query::type_op::{QueryTypeOp, TypeOp, TypeOpOutput}; use smallvec::SmallVec; use tracing::{debug, instrument}; @@ -115,12 +120,14 @@ pub fn provide(providers: &mut Providers) { fn mir_borrowck( tcx: TyCtxt<'_>, def: LocalDefId, -) -> Result<&FxIndexMap>, ErrorGuaranteed> { +) -> Result<&rustc_middle::mir::BorrowCheckResult<'_>, ErrorGuaranteed> { assert!(!tcx.is_typeck_child(def.to_def_id())); if tcx.is_trivial_const(def) { debug!("Skipping borrowck because of trivial const"); - let opaque_types = Default::default(); - return Ok(tcx.arena.alloc(opaque_types)); + return Ok(tcx.arena.alloc(rustc_middle::mir::BorrowCheckResult { + opaque_types: Default::default(), + coroutine_nll_constraints: Default::default(), + })); } let (input_body, _) = tcx.mir_promoted(def); debug!("run query mir_borrowck: {}", tcx.def_path_str(def)); @@ -128,6 +135,11 @@ fn mir_borrowck( // We should eagerly check stalled coroutine obligations from HIR typeck. // Not doing so leads to silent normalization failures later, which will // fail to register opaque types in the next solver. + // + // When dxf is active, check_coroutine_obligations uses a special typing + // mode that delays auto-trait goals on coroutine witnesses. These delayed + // goals are re-evaluated later inside mir_borrowck (before finalize), + // allowing try_hydrate_coroutine_witness_scc to access SCC data. tcx.ensure_result().check_coroutine_obligations(def)?; let input_body: &Body<'_> = &input_body.borrow(); @@ -136,12 +148,19 @@ fn mir_borrowck( Err(guar) } else if input_body.should_skip() { debug!("Skipping borrowck because of injected body"); - let opaque_types = Default::default(); - Ok(tcx.arena.alloc(opaque_types)) + Ok(tcx.arena.alloc(rustc_middle::mir::BorrowCheckResult { + opaque_types: Default::default(), + coroutine_nll_constraints: Default::default(), + })) } else { let tainted_by_errors = Default::default(); let mut root_cx = BorrowCheckRootCtxt::new(tcx, def, None, &tainted_by_errors); root_cx.do_mir_borrowck(); + if tainted_by_errors.get().is_none() && tcx.dxf() { + if let Err(err) = resolve_deferred_coroutine_goals(tcx, def, root_cx.hidden_types()) { + tainted_by_errors.set(Some(err)); + } + } root_cx.finalize() } } @@ -444,6 +463,139 @@ fn borrowck_check_region_constraints<'diag, 'tcx>( polonius_context, ); + // Extract SCC data for coroutine witnesses. + // This enables merging bound variables that NLL proves are equal, + // fixing false Send/Sync failures (see #110338). + if body.coroutine.is_some() { + // Always feed coroutine_witness_scc_data so the query doesn't panic + // when read in post-borrowck modes such as PostAnalysis, Codegen. + // The actual SCC analysis is only computed when dxf() is enabled. + let layout = if tcx.dxf() { tcx.mir_coroutine_witnesses(def.to_def_id()) } else { None }; + let nll_data = if let Some(layout) = layout { + let sccs = regioncx.constraint_sccs(); + // For each witness field, use reverse_local_map + // to look up the original body local. The body local has + // RegionVid regions that we can extract for SCC comparison. + let mut region_vids = vec![]; + + for (saved_local, decl) in layout.field_tys.iter_enumerated() { + if decl.ignore_for_traits { + continue; + } + // Use the reverse_local_map to find the original Local. + let local = layout.reverse_local_map[saved_local]; + let local_decl = &body.local_decls[local]; + // Visit (not fold) — we only need to collect RegionVids, + // not rebuild the type. + struct RegionVidCollector<'a>(&'a mut Vec); + impl<'tcx> ty::TypeVisitor> for RegionVidCollector<'_> { + fn visit_region(&mut self, r: ty::Region<'tcx>) { + if let ty::ReVar(vid) = r.kind() { + self.0.push(vid); + } + } + } + local_decl.ty.visit_with(&mut RegionVidCollector(&mut region_vids)); + } + + // Build SCC groups: map each SCC id to the list of witness + // bound regions belonging to it. The first element of each + // group is the representative. + let mut scc_group_map = FxIndexMap::<_, SmallVec<[_; 2]>>::default(); + for (idx, &vid) in region_vids.iter().enumerate() { + let br = ty::Region::new_bound( + tcx, + ty::INNERMOST, + ty::BoundRegion { + var: ty::BoundVar::from_usize(idx), + kind: ty::BoundRegionKind::Anon, + }, + ); + scc_group_map.entry(sccs.scc(vid)).or_default().push(br); + } + // Build outlives edges between witness bound regions + // using NLL region values. + // Build a mapping: universal_region_vid -> witness representative region. + let mut ur_to_rep = UnordMap::default(); + for (&scc, members) in &scc_group_map { + let rep = members[0]; + for ur in regioncx.universal_regions_outlived_by_scc(scc) { + ur_to_rep.entry(ur).or_insert(rep); + } + } + + // For each distinct witness SCC, check which universal + // regions it outlives. Emit outlives edges and 'static bounds. + let mut outlives_edges = vec![]; + let mut outlives_static = vec![]; + let fr_static = regioncx.universal_regions().fr_static; + for (&scc, members) in &scc_group_map { + let rep = members[0]; + + for ur in regioncx.universal_regions_outlived_by_scc(scc) { + if ur == fr_static { + outlives_static.push(rep); + } + if let Some(&target_rep) = ur_to_rep.get(&ur) { + if rep != target_rep { + outlives_edges.push((rep, target_rep)); + } + } + } + } + + // Containment-based outlives. + for (&scc_i, members_i) in &scc_group_map { + for (&scc_j, members_j) in &scc_group_map { + if scc_i == scc_j { + continue; + } + if regioncx.scc_values_contain(scc_i, scc_j) { + let rep_i = members_i[0]; + let rep_j = members_j[0]; + outlives_edges.push((rep_i, rep_j)); + } + } + } + + // Emit SCC equivalence edges as directed cycles. + for members in scc_group_map.into_values() { + if members.len() < 2 { + continue; + } + for window in members.windows(2) { + outlives_edges.push((window[0], window[1])); + } + outlives_edges.push((members[members.len() - 1], members[0])); + } + + debug!( + ?def, + ?region_vids, + ?outlives_edges, + ?outlives_static, + "coroutine nll hydration", + ); + + // Build OutlivesClause assumptions directly from regions. + let preds: Vec<_> = outlives_edges + .into_iter() + .map(|(sup, sub)| ty::OutlivesClause(sup.into(), sub)) + .chain( + outlives_static + .into_iter() + .map(|r| ty::OutlivesClause(r.into(), tcx.lifetimes.re_static)), + ) + .collect(); + + CoroutineNllOutlives { assumptions: tcx.mk_outlives(&preds) } + } else { + CoroutineNllOutlives { assumptions: tcx.mk_outlives(&[]) } + }; + root_cx.coroutine_nll_constraints.insert(def, nll_data); + tcx.feed_coroutine_witness_scc_data(def, nll_data); + } + // Dump MIR results into a file, if that is enabled. This lets us // write unit-tests, as well as helping with debugging. nll::dump_nll_mir(&infcx, body, ®ioncx, &opt_closure_req, &borrow_set); @@ -2767,3 +2919,239 @@ enum Overlap { /// will also be disjoint. Disjoint, } + +#[instrument(level = "debug", skip(tcx))] +fn build_coroutine_constraints<'tcx>( + tcx: TyCtxt<'tcx>, + coro_def_id: LocalDefId, +) -> Option> { + let nll_outlives = tcx + .coroutine_witness_scc_data(coro_def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); + + let hidden_types = tcx.coroutine_hidden_types(coro_def_id.to_def_id()); + let identity_args = + rustc_middle::ty::GenericArgs::identity_for_item(tcx, coro_def_id.to_def_id()); + let binder = hidden_types.instantiate(tcx, identity_args).skip_norm_wip(); + let bound_vars = binder.bound_vars(); + let num_vars = bound_vars.len(); + debug!(?coro_def_id, ?binder, num_vars); + + if num_vars == 0 { + return None; + } + + // Keep existing assumptions from typeck WF as-is. + let witness = binder.skip_binder(); + let new_assumptions: SmallVec<[_; 2]> = + witness.assumptions.iter().chain(&*nll_outlives.assumptions).collect(); + if new_assumptions.is_empty() { + None + } else { + Some(rustc_middle::ty::CoroutineRegionConstraints( + rustc_middle::ty::Binder::bind_with_vars(tcx.mk_outlives(&new_assumptions), bound_vars), + )) + } +} + +#[instrument(level = "debug", skip(tcx, borrowck_hidden_types))] +fn resolve_deferred_coroutine_goals<'tcx>( + tcx: TyCtxt<'tcx>, + def: LocalDefId, + borrowck_hidden_types: &rustc_data_structures::fx::FxIndexMap< + LocalDefId, + ty::DefinitionSiteHiddenType<'tcx>, + >, +) -> Result<(), rustc_errors::ErrorGuaranteed> { + let typeck_results = tcx.typeck(def); + if typeck_results.coroutine_stalled_predicates.is_empty() { + return Ok(()); + } + + // Collect local opaque types from stalled predicates so we can + // reveal them when normalizing. This is needed because stalled predicates + // may reference opaque types (e.g. `impl Future: Send`) that hide + // coroutine types underneath. + let mut expanded_opaque_types: Vec = tcx.opaque_types_defined_by(def).to_vec(); + struct OpaqueCollector<'a>(&'a mut Vec); + impl<'tcx> TypeVisitor> for OpaqueCollector<'_> { + fn visit_ty(&mut self, t: Ty<'tcx>) { + if let ty::Alias(_, alias_ty) = t.kind() + && let ty::AliasTyKind::Opaque { def_id } = alias_ty.kind + && let Some(local_def_id) = def_id.as_local() + && !self.0.contains(&local_def_id) + { + self.0.push(local_def_id); + } + t.super_visit_with(self); + } + } + let mut collector = OpaqueCollector(&mut expanded_opaque_types); + for (predicate, _) in &typeck_results.coroutine_stalled_predicates { + predicate.visit_with(&mut collector); + } + debug!(?expanded_opaque_types); + + // Discover coroutines from stalled predicates and opaque hidden types. + let mut coroutines = FxIndexSet::default(); + struct CoroutineCollector<'a>(&'a mut FxIndexSet); + impl<'tcx> TypeVisitor> for CoroutineCollector<'_> { + fn visit_ty(&mut self, t: Ty<'tcx>) { + if let ty::CoroutineWitness(coro_def_id, _) | ty::Coroutine(coro_def_id, _) = t.kind() + && let Some(local_id) = coro_def_id.as_local() + { + self.0.insert(local_id); + } + t.super_visit_with(self); + } + } + for (predicate, _) in &typeck_results.coroutine_stalled_predicates { + predicate.visit_with(&mut CoroutineCollector(&mut coroutines)); + } + // Walk hidden types of def's own opaques from borrowck results (no cycle). + for (_, hidden_ty) in borrowck_hidden_types { + hidden_ty.ty.instantiate_identity().visit_with(&mut CoroutineCollector(&mut coroutines)); + } + // Walk hidden types of expanded opaques beyond def's own set via type_of. + let orig_opaque_count = tcx.opaque_types_defined_by(def).len(); + for &opaque_def_id in &expanded_opaque_types[orig_opaque_count..] { + let hidden_ty = tcx.type_of(opaque_def_id).skip_binder(); + debug!(?opaque_def_id, ?hidden_ty); + hidden_ty.visit_with(&mut CoroutineCollector(&mut coroutines)); + } + debug!(?coroutines); + + // Use PostBorrowck with all opaques in scope. To avoid a query cycle + // by solving a goal involving an opaque under root DefId, we pre-populate + // the InferCtxt's opaque type storage with hidden types already computed + // by do_mir_borrowck(). The solver checks storage read-only before + // falling back to type_of, so the cycle is avoided. + let defined_opaque_types = tcx.mk_local_def_ids(&expanded_opaque_types); + let mode = ty::TypingMode::PostBorrowck { defined_opaque_types }; + let infcx = tcx.infer_ctxt().build(mode); + // Only inject hidden types for def's own opaques into storage. + // These are the ones that would cause a type_of → mir_borrowck cycle. + // Other opaques can be resolved via type_of without cycling. + let self_opaques = tcx.opaque_types_defined_by(def); + for (&opaque_def_id, hidden_ty) in borrowck_hidden_types { + if !self_opaques.contains(&opaque_def_id) { + continue; + } + let args = ty::GenericArgs::identity_for_item(tcx, opaque_def_id); + let key = ty::OpaqueTypeKey { def_id: opaque_def_id, args }; + let ty = hidden_ty.ty.instantiate_identity(); + // Replace erased regions with fresh region vars. + // This is also what the solver does in PostBorrowck mode. + let ty = fold_regions(tcx, ty, |re, _| match re.kind() { + ty::ReErased => infcx.next_region_var(RegionVariableOrigin::Misc(hidden_ty.span)), + _ => re, + }) + .skip_norm_wip(); + let provisional = ty::ProvisionalHiddenType { span: hidden_ty.span, ty }; + infcx.register_hidden_type_in_storage(key, provisional); + } + + let ocx = ObligationCtxt::new_with_diagnostics(&infcx); + let param_env = tcx.param_env(def); + // Build enriched ParamEnv. + let mut clauses = param_env.caller_bounds().to_vec(); + let mut coroutines_without_constraints = vec![]; + for &coro_id in &coroutines { + // Ensure borrowck has run for this coroutine's root so that + // coroutine_witness_scc_data has been fed. + // For same-function coroutines (coro_root == def), the data was + // already fed during do_mir_borrowck() which ran before this point. + let coro_root = tcx.typeck_root_def_id_local(coro_id); + if coro_root != def && tcx.ensure_result().mir_borrowck(coro_root).is_err() { + continue; + } + let constraints = build_coroutine_constraints(tcx, coro_id); + debug!(?coro_id, ?constraints); + if constraints.is_none() { + coroutines_without_constraints.push(coro_id); + } + if let Some(constraints) = constraints { + let pred_kind = ty::PredicateKind::Clause( + ty::ClauseKind::CoroutineWitnessRegionConstraints(coro_id.to_def_id(), constraints), + ); + let predicate: ty::Predicate<'tcx> = ty::Binder::dummy(pred_kind).upcast(tcx); + let clause = predicate.expect_clause(); + clauses.push(clause); + } + } + + let enriched_param_env = ty::ParamEnv::new(tcx.mk_clauses(&clauses)); + debug!(?enriched_param_env); + for (predicate, cause) in &typeck_results.coroutine_stalled_predicates { + debug!(?predicate, "register"); + ocx.register_obligation(Obligation::new( + tcx, + cause.clone(), + enriched_param_env, + *predicate, + )); + } + + let errors = ocx.evaluate_obligations_error_on_ambiguity(); + debug!(?errors); + // Drain opaque types from storage. We pre-populated them for cycle avoidance + // and the solver may have added more during evaluation. The defining uses + // are handled by borrowck itself, not by this deferred resolution. + let _ = infcx.take_opaque_types(); + let mut errors = match errors { + TraitErrors::NoErrors => { + let result = ocx.resolve_regions_and_report_errors(def, enriched_param_env, []); + let _ = infcx.take_opaque_types(); + return result; + } + TraitErrors::HasErrors(errors) => errors, + }; + // Adjust obligation causes to include FunctionArg info so that + // error reporting can show "required by a bound introduced by + // this call" notes. The stalled predicates have WhereClauseInExpr + // causes that contain the call HirId, but without FunctionArg + // wrapping the note won't appear. + for error in &mut errors { + // Check both the error's obligation and the root obligation + // for WhereClauseInExpr cause code. + let root_code = error.root_obligation.cause.code().peel_derives(); + if let ObligationCauseCode::WhereClauseInExpr(_def_id, _span, call_hir_id, _idx) = + *root_code + { + // Use the root obligation's call_hir_id for both. + // The arg_hir_id ideally should point to the argument + // expression, but we approximate with call_hir_id. + error.obligation.cause.map_code(|parent_code| ObligationCauseCode::FunctionArg { + arg_hir_id: call_hir_id, + call_hir_id, + parent_code, + }); + } + } + let err = infcx.err_ctxt().report_fulfillment_errors(errors); + + // If any coroutines had unprovable region constraints, add a + // note explaining that NLL couldn't establish the needed + // lifetime relationships. + if !coroutines_without_constraints.is_empty() { + for &coro_id in &coroutines_without_constraints { + let hidden_types = tcx.coroutine_hidden_types(coro_id.to_def_id()); + let identity_args = ty::GenericArgs::identity_for_item(tcx, coro_id.to_def_id()); + let binder = hidden_types.instantiate(tcx, identity_args).skip_norm_wip(); + let num_vars = binder.bound_vars().len(); + if num_vars > 0 { + let coro_span = tcx.def_span(coro_id); + tcx.dcx().span_note( + coro_span, + "this `async` fn holds references with different lifetimes across \ + an `.await` point; the `Send` trait requires these lifetimes to \ + satisfy certain bounds, but the borrow checker could not verify them", + ); + } + } + } + + Err(err) +} diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index d86ba5d462de1..fa6448d9f8d85 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -478,6 +478,19 @@ impl<'tcx> RegionInferenceContext<'tcx> { self.scc_values.placeholders_contained_in(scc) } + /// Returns `true` if the SCC value of `sup_scc` is a superset of `sub_scc`'s + /// value across all components (CFG points, universals, placeholders). + pub(crate) fn scc_values_contain( + &self, + sup_scc: ConstraintSccIndex, + sub_scc: ConstraintSccIndex, + ) -> bool { + if sup_scc == sub_scc { + return true; + } + self.scc_values.contains_region_values(sup_scc, sub_scc) + } + /// Performs region inference and report errors if we see any /// unsatisfiable constraints. If this is a closure, returns the /// region requirements to propagate to our creator, if any. @@ -1862,6 +1875,14 @@ impl<'tcx> RegionInferenceContext<'tcx> { &self.constraint_sccs } + /// Returns the universal regions outlived by the given SCC. + pub fn universal_regions_outlived_by_scc( + &self, + scc: ConstraintSccIndex, + ) -> impl Iterator { + self.scc_values.universal_regions_outlived_by(scc) + } + /// Returns the representative `RegionVid` for a given SCC. /// See `RegionTracker` for how a region variable ID is chosen. /// diff --git a/compiler/rustc_borrowck/src/region_infer/values.rs b/compiler/rustc_borrowck/src/region_infer/values.rs index 841e5713751cd..c468febed7ead 100644 --- a/compiler/rustc_borrowck/src/region_infer/values.rs +++ b/compiler/rustc_borrowck/src/region_infer/values.rs @@ -344,6 +344,42 @@ impl<'tcx, N: Idx> RegionValues<'tcx, N> { } } + /// Returns `true` if `sup_region`'s value is a superset of `sub_region`'s + /// value across ALL components: CFG points, free (universal) regions, and + /// placeholders. This is the full outlives check for NLL region values. + pub(crate) fn contains_region_values(&self, sup_region: N, sub_region: N) -> bool { + // Short-circuit on CFG points first. + if !self.contains_points(sup_region, sub_region) { + return false; + } + + // Check universal (free) regions. + if let Some(sub_row) = self.free_regions.row(sub_region) + && !sub_row.is_empty() + { + let Some(sup_row) = self.free_regions.row(sup_region) else { + return false; + }; + if !sup_row.superset(sub_row) { + return false; + } + } + + // Check placeholders. + if let Some(sub_row) = self.placeholders.row(sub_region) + && !sub_row.is_empty() + { + let Some(sup_row) = self.placeholders.row(sup_region) else { + return false; + }; + if !sup_row.superset(sub_row) { + return false; + } + } + + true + } + /// Returns the locations contained within a given region `r`. pub(crate) fn locations_outlived_by(&self, r: N) -> impl Iterator { self.points diff --git a/compiler/rustc_borrowck/src/root_cx.rs b/compiler/rustc_borrowck/src/root_cx.rs index b1aca758d64ae..c1c15607e8d9e 100644 --- a/compiler/rustc_borrowck/src/root_cx.rs +++ b/compiler/rustc_borrowck/src/root_cx.rs @@ -42,6 +42,8 @@ pub(super) struct BorrowCheckRootCtxt<'diag, 'tcx: 'diag> { collect_region_constraints_results: FxIndexMap>, propagated_borrowck_results: FxHashMap>, + pub(super) coroutine_nll_constraints: + FxIndexMap>, tainted_by_errors: &'diag Cell>, /// This should be `None` during normal compilation. See [`crate::consumers`] for more /// information on how this is used. @@ -62,6 +64,7 @@ impl<'diag, 'tcx> BorrowCheckRootCtxt<'diag, 'tcx> { unconstrained_hidden_type_errors: Default::default(), collect_region_constraints_results: Default::default(), propagated_borrowck_results: Default::default(), + coroutine_nll_constraints: Default::default(), tainted_by_errors, consumer, } @@ -79,6 +82,12 @@ impl<'diag, 'tcx> BorrowCheckRootCtxt<'diag, 'tcx> { self.tcx.dcx().taintable_handle(&self.tainted_by_errors) } + pub(super) fn hidden_types( + &self, + ) -> &FxIndexMap> { + &self.hidden_types + } + pub(super) fn used_mut_upvars( &self, nested_body_def_id: LocalDefId, @@ -88,12 +97,14 @@ impl<'diag, 'tcx> BorrowCheckRootCtxt<'diag, 'tcx> { pub(super) fn finalize( self, - ) -> Result<&'tcx FxIndexMap>, ErrorGuaranteed> - { + ) -> Result<&'tcx rustc_middle::mir::BorrowCheckResult<'tcx>, ErrorGuaranteed> { if let Some(guar) = self.tainted_by_errors.get() { Err(guar) } else { - Ok(self.tcx.arena.alloc(self.hidden_types)) + Ok(self.tcx.arena.alloc(rustc_middle::mir::BorrowCheckResult { + opaque_types: self.hidden_types, + coroutine_nll_constraints: self.coroutine_nll_constraints, + })) } } diff --git a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs index 739abd2613748..df8772a51819d 100644 --- a/compiler/rustc_const_eval/src/const_eval/eval_queries.rs +++ b/compiler/rustc_const_eval/src/const_eval/eval_queries.rs @@ -43,6 +43,7 @@ fn retry_codegen_mode_with_postanalysis<'tcx, K: TypeVisitable>, V> ty::TypingMode::Coherence | ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::Reflection | ty::TypingMode::PostAnalysis => {} diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs index 7fe32b4e75ffb..8c438dbc59c43 100644 --- a/compiler/rustc_const_eval/src/lib.rs +++ b/compiler/rustc_const_eval/src/lib.rs @@ -35,6 +35,7 @@ fn assert_typing_mode(typing_mode: ty::TypingMode<'_>) { | ty::TypingMode::Typeck { .. } | ty::TypingMode::Reflection | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } => bug!( "Const eval should always happens in PostAnalysis or Codegen mode. See the comment on `assert_typing_mode` for more details." ), diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs index d98125f7cd9f9..a5f8225784a20 100644 --- a/compiler/rustc_hir_analysis/src/check/check.rs +++ b/compiler/rustc_hir_analysis/src/check/check.rs @@ -466,7 +466,7 @@ fn best_definition_site_of_opaque<'tcx>( .tcx .mir_borrowck(item_def_id) .ok() - .and_then(|opaque_types| opaque_types.get(&self.opaque_def_id)) + .and_then(|result| result.opaque_types.get(&self.opaque_def_id)) { ControlFlow::Break((hidden_ty.span, item_def_id)) } else { @@ -2271,7 +2271,13 @@ pub(super) fn check_coroutine_obligations( debug!(?typeck_results.coroutine_stalled_predicates); - let mode = if tcx.next_trait_solver_globally() { + let mode = if tcx.dxf() { + // When dxf is active and called from mir_borrowck, NLL data + // is not yet available. + // Use BorrowckPendingScc to stall coroutine auto-trait goals + // as pending. + TypingMode::borrowck_pending_scc(tcx, def_id) + } else if tcx.next_trait_solver_globally() { // This query is conceptually between HIR typeck and // MIR borrowck. We use the opaque types defined by HIR // and ignore region constraints. @@ -2291,7 +2297,14 @@ pub(super) fn check_coroutine_obligations( ocx.register_obligation(Obligation::new(tcx, cause.clone(), param_env, *predicate)); } - let errors = ocx.evaluate_obligations_error_on_ambiguity(); + // In BorrowckPendingScc mode, coroutine auto-trait goals are expected + // to stall awaiting NLL data (StalledOnCoroutines::Yes). + // Evaluate without treating ambiguity as a hard error. + let errors = if mode.is_borrowck_pending_scc() { + ocx.try_evaluate_obligations() + } else { + ocx.evaluate_obligations_error_on_ambiguity() + }; debug!(?errors); if let TraitErrors::HasErrors(errors) = errors { return Err(infcx.err_ctxt().report_fulfillment_errors(errors)); diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 9a498837b1f4d..56edfe38f4a81 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -2294,7 +2294,8 @@ impl<'tcx> WfCheckingCtxt<'_, 'tcx> { // regular WF checking ty::ClauseKind::WellFormed(..) // Unstable feature goals cannot be proven in an empty environment so skip them - | ty::ClauseKind::UnstableFeature(..) => continue, + | ty::ClauseKind::UnstableFeature(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => continue, _ => {} } diff --git a/compiler/rustc_hir_analysis/src/collect/clauses_of.rs b/compiler/rustc_hir_analysis/src/collect/clauses_of.rs index 1ee647fd61a33..ce9f2a2416d86 100644 --- a/compiler/rustc_hir_analysis/src/collect/clauses_of.rs +++ b/compiler/rustc_hir_analysis/src/collect/clauses_of.rs @@ -796,6 +796,7 @@ pub(super) fn assert_only_contains_clauses_from<'tcx>( | ty::ClauseKind::ConstArgHasType(_, _) | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::UnstableFeature(_) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) | ty::ClauseKind::ConstEvaluatable(_) => { bug!( "unexpected non-`Self` predicate when computing \ @@ -824,6 +825,7 @@ pub(super) fn assert_only_contains_clauses_from<'tcx>( | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(_) | ty::ClauseKind::UnstableFeature(_) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) | ty::ClauseKind::HostEffect(..) => { bug!( "unexpected non-`Self` predicate when computing \ diff --git a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs index 88936b375a12f..3d5030a98cd32 100644 --- a/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs +++ b/compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs @@ -185,8 +185,8 @@ impl<'tcx> TaitConstraintLocator<'tcx> { } DefiningScopeKind::MirBorrowck => match tcx.mir_borrowck(item_def_id) { Err(guar) => self.insert_found(ty::DefinitionSiteHiddenType::new_error(tcx, guar)), - Ok(hidden_types) => { - if let Some(&hidden_type) = hidden_types.get(&self.def_id) { + Ok(result) => { + if let Some(&hidden_type) = result.opaque_types.get(&self.def_id) { debug!(?hidden_type, "found constraint"); self.insert_found(hidden_type); } else if let Err(guar) = @@ -269,8 +269,8 @@ pub(super) fn find_opaque_ty_constraints_for_rpit<'tcx>( } } DefiningScopeKind::MirBorrowck => match tcx.mir_borrowck(owner_def_id) { - Ok(hidden_types) => { - if let Some(hidden_ty) = hidden_types.get(&def_id) { + Ok(result) => { + if let Some(hidden_ty) = result.opaque_types.get(&def_id) { hidden_ty.ty } else { let hir_ty = tcx.type_of_opaque_hir_typeck(def_id); diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs index 5788983811f03..9928afca0d967 100644 --- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs @@ -473,6 +473,7 @@ fn trait_specialization_kind<'tcx>( | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(..) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::HostEffect(..) => None, + | ty::ClauseKind::HostEffect(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => None, } } diff --git a/compiler/rustc_hir_analysis/src/outlives/explicit.rs b/compiler/rustc_hir_analysis/src/outlives/explicit.rs index ffbbb316e5c1f..375374dc10cbb 100644 --- a/compiler/rustc_hir_analysis/src/outlives/explicit.rs +++ b/compiler/rustc_hir_analysis/src/outlives/explicit.rs @@ -43,7 +43,8 @@ impl<'tcx> ExplicitClausesMap<'tcx> { | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(_) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::HostEffect(..) => {} + | ty::ClauseKind::HostEffect(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => {} } } diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs index 8ff4c3bf28c34..5c489496d1667 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/_impl.rs @@ -726,6 +726,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ty::TypingMode::Coherence | ty::TypingMode::Reflection | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::PostAnalysis | ty::TypingMode::Codegen => { @@ -733,9 +734,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - if defining_opaque_types_and_generators - .iter() - .any(|def_id| self.tcx.is_coroutine(def_id.to_def_id())) + if self.tcx.sess.opts.unstable_opts.dxf + || defining_opaque_types_and_generators + .iter() + .any(|def_id| self.tcx.is_coroutine(def_id.to_def_id())) { self.typeck_results.borrow_mut().coroutine_stalled_predicates.extend( self.fulfillment_cx diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs index e6de8b55ef2f9..5c480ae6d9787 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/inspect_obligations.rs @@ -79,6 +79,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { | ty::PredicateKind::ConstEquate(..) | ty::PredicateKind::Clause(ty::ClauseKind::HostEffect(..)) | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints(..)) | ty::PredicateKind::Ambiguous => false, } } diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index b2e0a3bd7a195..81e44a6484a73 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -1046,7 +1046,8 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(_) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::HostEffect(..) => None, + | ty::ClauseKind::HostEffect(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => None, } }); diff --git a/compiler/rustc_hir_typeck/src/opaque_types.rs b/compiler/rustc_hir_typeck/src/opaque_types.rs index 17e193d7f44ab..dbd1aa48947db 100644 --- a/compiler/rustc_hir_typeck/src/opaque_types.rs +++ b/compiler/rustc_hir_typeck/src/opaque_types.rs @@ -105,6 +105,7 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ty::TypingMode::Coherence | ty::TypingMode::Reflection | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::PostAnalysis | ty::TypingMode::Codegen => { diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index d27b28dbdb214..374fb025caca8 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -57,7 +57,10 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { &self, u: ty::UniverseIndex, ) -> Option>> { - self.placeholder_assumptions_for_next_solver.borrow().get(&u).unwrap().as_ref().cloned() + self.placeholder_assumptions_for_next_solver + .borrow() + .get(&u) + .and_then(|v| v.as_ref().cloned()) } fn get_solver_region_constraint( @@ -426,6 +429,14 @@ impl<'tcx> rustc_type_ir::InferCtxtLike for InferCtxt<'tcx> { ty::ProvisionalHiddenType { span, ty: hidden_ty }, ) } + + fn lookup_hidden_type_in_storage( + &self, + opaque_type_key: &ty::OpaqueTypeKey<'tcx>, + ) -> Option> { + self.lookup_hidden_type_in_storage(opaque_type_key) + } + fn add_duplicate_opaque_type( &self, opaque_type_key: ty::OpaqueTypeKey<'tcx>, diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 8ae2872f21af5..35ca37b4ce72f 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -363,7 +363,7 @@ impl<'tcx> Drop for InferCtxt<'tcx> { | TypingMode::Codegen => {} // In erased mode, the opaque type storage is always empty TypingMode::ErasedNotCoherence(..) => {} - TypingMode::PostTypeckUntilBorrowck { .. } => { + TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::BorrowckPendingScc { .. } => { if !self.considering_regions { return; } @@ -1176,9 +1176,11 @@ impl<'tcx> InferCtxt<'tcx> { debug_assert!(!self.next_trait_solver()); match self.typing_mode_raw().assert_not_erased() { TypingMode::Typeck { defining_opaque_types_and_generators: defining_opaque_types } - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } => { - id.into().as_local().is_some_and(|def_id| defining_opaque_types.contains(&def_id)) - } + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, .. } + | TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: defining_opaque_types, + .. + } => id.into().as_local().is_some_and(|def_id| defining_opaque_types.contains(&def_id)), // FIXME(#132279): This function is quite weird in post-analysis // and post-borrowck analysis mode. We may need to modify its uses // to support PostBorrowck in the old solver as well. @@ -1555,9 +1557,8 @@ impl<'tcx> InferCtxt<'tcx> { // errors and fail to reveal opaques while inside of bodies. We should rename this // function and require explicit comments on all use-sites in the future. ty::TypingMode::Typeck { defining_opaque_types_and_generators: _ } - | ty::TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } => { - TypingMode::non_body_analysis() - } + | ty::TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _, .. } + | ty::TypingMode::BorrowckPendingScc { .. } => TypingMode::non_body_analysis(), mode @ (ty::TypingMode::Coherence | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::PostAnalysis diff --git a/compiler/rustc_infer/src/infer/opaque_types/mod.rs b/compiler/rustc_infer/src/infer/opaque_types/mod.rs index 08c7c49417124..9b145705065da 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/mod.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/mod.rs @@ -208,6 +208,16 @@ impl<'tcx> InferCtxt<'tcx> { self.inner.borrow_mut().opaque_types().register(opaque_type_key, hidden_ty) } + /// Look up a previously registered hidden type for the given opaque type key. + /// Returns `None` if no hidden type has been registered. + /// This is a read-only operation — it does not modify storage. + pub fn lookup_hidden_type_in_storage( + &self, + opaque_type_key: &OpaqueTypeKey<'tcx>, + ) -> Option> { + self.inner.borrow().opaque_type_storage.get(opaque_type_key) + } + /// Insert a hidden type into the opaque type storage, equating it /// with any previous entries if necessary. /// @@ -254,7 +264,8 @@ impl<'tcx> InferCtxt<'tcx> { ); } } - ty::TypingMode::PostTypeckUntilBorrowck { .. } => { + ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } => { let prev = self .inner .borrow_mut() diff --git a/compiler/rustc_infer/src/infer/opaque_types/table.rs b/compiler/rustc_infer/src/infer/opaque_types/table.rs index 066d12be320a2..76213766af4e5 100644 --- a/compiler/rustc_infer/src/infer/opaque_types/table.rs +++ b/compiler/rustc_infer/src/infer/opaque_types/table.rs @@ -58,6 +58,12 @@ impl<'tcx> OpaqueTypeStorage<'tcx> { opaque_types.is_empty() && duplicate_entries.is_empty() } + /// Look up a previously registered hidden type for the given opaque type key. + /// Returns `None` if no hidden type has been registered for this key. + pub fn get(&self, key: &OpaqueTypeKey<'tcx>) -> Option> { + self.opaque_types.get(key).map(|entry| entry.ty) + } + pub(crate) fn take_opaque_types( &mut self, ) -> impl Iterator, ProvisionalHiddenType<'tcx>)> { diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index 627fb962d5dbf..1a9cc06de4ee4 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1437,7 +1437,8 @@ impl<'tcx> LateLintPass<'tcx> for TrivialConstraints { // FIXME(generic_const_exprs): `ConstEvaluatable` can be written | ClauseKind::ConstEvaluatable(..) // Users don't write this directly, only via another trait ref. - | ty::ClauseKind::HostEffect(..) => continue, + | ty::ClauseKind::HostEffect(..) + | ClauseKind::CoroutineWitnessRegionConstraints(..) => continue, }; if clause.is_global() { cx.emit_span_lint( diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 3f73b14fb85ee..be76de6d38241 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -280,6 +280,26 @@ provide! { tcx, def_id, other, cdata, asyncness => { table_direct } fn_arg_idents => { table } coroutine_kind => { table_direct } + coroutine_witness_scc_data => { + let value = cdata + .root + .tables + .coroutine_witness_scc_data + .get(cdata, def_id.index); + let data = match value { + Some(v) => v.decode((cdata, tcx)), + None => rustc_middle::mir::CoroutineNllOutlives { + assumptions: tcx.mk_outlives(&[]), + }, + }; + ty::EarlyBinder::bind( + tcx, + ty::Binder::bind_with_vars( + data, + tcx.coroutine_hidden_types(def_id).skip_binder().bound_vars(), + ) + ) + } coroutine_for_closure => { table } coroutine_by_move_body_def_id => { table } eval_static_initializer => { diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index b32a23f53f8cc..7963289cab7e8 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -1550,7 +1550,22 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> { if let DefKind::Closure | DefKind::SyntheticCoroutineBody = def_kind && let Some(coroutine_kind) = self.tcx.coroutine_kind(def_id) { - self.tables.coroutine_kind.set(def_id.index, Some(coroutine_kind)) + self.tables.coroutine_kind.set(def_id.index, Some(coroutine_kind)); + // Encode NLL-derived SCC data for coroutine witnesses. + // This is fed during mir_borrowck and needs to be available cross-crate + // for try_hydrate_coroutine_witness_scc. + if self.tcx.sess.opts.unstable_opts.dxf { + let scc_data = self + .tcx + .coroutine_witness_scc_data(def_id) + .instantiate_identity() + .skip_norm_wip() + .skip_binder(); + if !scc_data.assumptions.is_empty() { + let lazy_scc = self.lazy(scc_data); + self.tables.coroutine_witness_scc_data.set(def_id.index, Some(lazy_scc)); + } + } } if def_kind == DefKind::Closure && tcx.type_of(def_id).skip_binder().is_coroutine_closure() diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 3273013466245..b53551419133a 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -454,6 +454,7 @@ define_tables! { rendered_precise_capturing_args: Table>>, fn_arg_idents: Table>>, coroutine_kind: Table, + coroutine_witness_scc_data: Table>>, coroutine_for_closure: Table, adt_destructor: Table>, adt_async_destructor: Table>, diff --git a/compiler/rustc_metadata/src/rmeta/parameterized.rs b/compiler/rustc_metadata/src/rmeta/parameterized.rs index 75b86f8dda274..15d7e4d7bdbd5 100644 --- a/compiler/rustc_metadata/src/rmeta/parameterized.rs +++ b/compiler/rustc_metadata/src/rmeta/parameterized.rs @@ -56,6 +56,10 @@ impl ParameterizedOverTcx for GenericArg<'static> { type Value<'tcx> = GenericArg<'tcx>; } +impl ParameterizedOverTcx for rustc_middle::mir::CoroutineNllOutlives<'static> { + type Value<'tcx> = rustc_middle::mir::CoroutineNllOutlives<'tcx>; +} + macro_rules! trivially_parameterized_over_tcx { ($($ty:ty),+ $(,)?) => { $( @@ -116,6 +120,7 @@ trivially_parameterized_over_tcx! { rustc_middle::middle::resolve_bound_vars::ObjectLifetimeDefault, rustc_middle::mir::ConstQualifs, rustc_middle::mir::ConstValue, + rustc_middle::ty::AnonConstKind, rustc_middle::ty::AssocContainer, rustc_middle::ty::AsyncDestructor, diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs index ef943d70c3ecf..f817462db189d 100644 --- a/compiler/rustc_middle/src/arena.rs +++ b/compiler/rustc_middle/src/arena.rs @@ -31,11 +31,7 @@ rustc_arena::declare_arena! { rustc_middle::mir::Body<'tcx> >, typeck_results: rustc_middle::ty::TypeckResults<'tcx>, - borrowck_result: - rustc_data_structures::fx::FxIndexMap< - rustc_hir::def_id::LocalDefId, - rustc_middle::ty::DefinitionSiteHiddenType<'tcx>, - >, + borrowck_result: rustc_middle::mir::BorrowCheckResult<'tcx>, resolver: rustc_data_structures::steal::Steal>, index_ast: rustc_index::IndexVec< diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 021c1c176d788..8e14d709fb3d4 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -559,8 +559,13 @@ fn write_coroutine_layout<'tcx>( w: &mut dyn io::Write, options: PrettyPrintMirOptions, ) -> io::Result<()> { - let CoroutineLayout { field_tys, variant_fields, variant_source_info, storage_conflicts } = - layout; + let CoroutineLayout { + field_tys, + reverse_local_map: _, + variant_fields, + variant_source_info, + storage_conflicts, + } = layout; writeln!(w, "{INDENT}coroutine layout {{")?; diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs index 616b1719359f1..67e846d2644ab 100644 --- a/compiler/rustc_middle/src/mir/query.rs +++ b/compiler/rustc_middle/src/mir/query.rs @@ -3,13 +3,15 @@ use std::fmt::{self, Debug}; use rustc_abi::{FieldIdx, VariantIdx}; +use rustc_data_structures::fx::FxIndexMap; use rustc_errors::ErrorGuaranteed; +use rustc_hir::def_id::LocalDefId; use rustc_index::IndexVec; use rustc_index::bit_set::BitMatrix; use rustc_macros::{StableHash, TyDecodable, TyEncodable, TypeFoldable, TypeVisitable}; use rustc_span::{Span, Symbol}; -use super::{ConstValue, SourceInfo}; +use super::{ConstValue, Local, SourceInfo}; use crate::ty::{self, CoroutineArgsExt, Ty}; rustc_index::newtype_index! { @@ -38,6 +40,13 @@ pub struct CoroutineLayout<'tcx> { /// The type of every local stored inside the coroutine. pub field_tys: IndexVec>, + /// Maps each `CoroutineSavedLocal` back to the original `Local` in the + /// MIR body. This is the reverse of the `saved_locals` bitset: the i-th + /// saved local corresponds to `reverse_local_map[i]` in the body. + #[type_foldable(identity)] + #[type_visitable(ignore)] + pub reverse_local_map: IndexVec, + /// Which of the above fields are in each variant. Note that one field may /// be stored in multiple variants. pub variant_fields: IndexVec>, @@ -81,6 +90,48 @@ impl Debug for CoroutineLayout<'_> { } } +/// NLL-derived outlives constraints for a coroutine's witness bound variables. +/// +/// Stores `OutlivesClause(sup_arg, sub_region)` assumptions using bound +/// regions that match the sequential region traversal order in +/// `coroutine_hidden_types`. These include: +/// - Equivalent regions, encoded as directed cycles. +/// - Outlives relationships from the maximised NLL constraint graph +/// - `'static` bounds for SCCs that outlive `'static` +/// - This one might be oddly specific, but we shall see. +/// +/// The `coroutine_witness_scc_data` query wraps this in +/// `EarlyBinder>`, which strictly matches +/// that of `coroutine_hidden_types`. +#[derive( + Copy, + Clone, + Debug, + Hash, + StableHash, + TyEncodable, + TyDecodable, + TypeFoldable, + TypeVisitable +)] +pub struct CoroutineNllOutlives<'tcx> { + /// All NLL-derived outlives assumptions for hydration. + pub assumptions: &'tcx ty::List>, +} + +/// The result of the `mir_borrowck` query. +/// +/// Contains opaque type definitions and NLL-derived outlives constraints +/// for coroutine witnesses. +#[derive(Debug, StableHash)] +pub struct BorrowCheckResult<'tcx> { + /// Opaque type definitions discovered during borrowck. + pub opaque_types: FxIndexMap>, + /// NLL-derived outlives constraints for each nested coroutine. + /// Keyed by the coroutine's `LocalDefId`. + pub coroutine_nll_constraints: FxIndexMap>, +} + /// The result of the `mir_const_qualif` query. /// /// Each field (except `tainted_by_errors`) corresponds to an implementer of the `Qualif` trait in diff --git a/compiler/rustc_middle/src/queries.rs b/compiler/rustc_middle/src/queries.rs index ca1cd2f45975f..30145d66610e9 100644 --- a/compiler/rustc_middle/src/queries.rs +++ b/compiler/rustc_middle/src/queries.rs @@ -1000,6 +1000,14 @@ rustc_queries! { desc { "looking up the hidden types stored across await points in a coroutine" } } + query coroutine_witness_scc_data( + def_id: DefId, + ) -> ty::EarlyBinder<'tcx, ty::Binder<'tcx, mir::CoroutineNllOutlives<'tcx>>> { + desc { "NLL-derived region constraints for coroutine witness" } + separate_provide_extern + feedable + } + /// Gets a map with the variances of every item in the local crate. /// ///
@@ -1239,10 +1247,11 @@ rustc_queries! { /// Borrow-checks the given typeck root, e.g. functions, const/static items, /// and its children, e.g. closures, inline consts. query mir_borrowck(key: LocalDefId) -> Result< - &'tcx FxIndexMap>, + &'tcx mir::BorrowCheckResult<'tcx>, ErrorGuaranteed > { desc { "borrow-checking `{}`", tcx.def_path_str(key) } + handle_cycle_error } /// Gets a complete map from all types to their inherent impls. diff --git a/compiler/rustc_middle/src/query/erase.rs b/compiler/rustc_middle/src/query/erase.rs index 9a6ab2732953a..7250eb6ec8c9c 100644 --- a/compiler/rustc_middle/src/query/erase.rs +++ b/compiler/rustc_middle/src/query/erase.rs @@ -234,6 +234,7 @@ impl_erasable_for_types_with_no_type_params! { rustc_middle::mono::MonoItemPartitions<'_>, rustc_middle::traits::query::MethodAutoderefStepsResult<'_>, rustc_middle::ty::AdtDef<'_>, + rustc_middle::ty::Binder<'_, rustc_middle::mir::CoroutineNllOutlives<'_>>, rustc_middle::ty::AnonConstKind, rustc_middle::ty::AssocItem, rustc_middle::ty::Asyncness, diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs index 33047bf3e67b4..57971911ff4a4 100644 --- a/compiler/rustc_middle/src/ty/codec.rs +++ b/compiler/rustc_middle/src/ty/codec.rs @@ -454,6 +454,15 @@ impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List { } } +impl<'tcx, D: TyDecoder<'tcx>> RefDecodable<'tcx, D> for ty::List> { + fn decode(decoder: &mut D) -> &'tcx Self { + let len = decoder.read_usize(); + decoder.interner().mk_outlives_from_iter( + (0..len).map::, _>(|_| Decodable::decode(decoder)), + ) + } +} + impl<'tcx, D: TyDecoder<'tcx>> Decodable for &'tcx ty::List { fn decode(d: &mut D) -> Self { RefDecodable::decode(d) @@ -470,6 +479,7 @@ impl_decodable_via_ref! { &'tcx ty::List>, &'tcx ty::ListWithCachedTypeInfo>, &'tcx ty::List>, + &'tcx ty::List>, } #[macro_export] diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 136a9e6ca464e..4d5e48e9a8dd2 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -621,6 +621,24 @@ impl<'tcx> TyCtxt<'tcx> { } TyCtxtFeed { tcx: self, key }.visibility(vis.to_mod_id()) } + + pub fn feed_coroutine_witness_scc_data( + self, + key: LocalDefId, + value: crate::mir::CoroutineNllOutlives<'tcx>, + ) { + debug_assert!(matches!( + self.def_kind(key), + DefKind::Closure | DefKind::SyntheticCoroutineBody + )); + // Get the witness binder's bound vars so the NLL data lives in + // the same binder namespace as the witness types. + let hidden_types = self.coroutine_hidden_types(key.to_def_id()); + let bound_vars = hidden_types.skip_binder().bound_vars(); + let binder = ty::Binder::bind_with_vars(value, bound_vars); + TyCtxtFeed { tcx: self, key } + .coroutine_witness_scc_data(ty::EarlyBinder::bind(self, binder)) + } } impl<'tcx, K: Copy> TyCtxtFeed<'tcx, K> { @@ -2715,6 +2733,10 @@ impl<'tcx> TyCtxt<'tcx> { self.sess.opts.unstable_opts.assumptions_on_binders } + pub fn dxf(self) -> bool { + self.sess.opts.unstable_opts.dxf && self.next_trait_solver_globally() + } + pub fn is_impl_trait_in_trait(self, def_id: DefId) -> bool { self.opt_rpitit_info(def_id).is_some() } diff --git a/compiler/rustc_middle/src/ty/context/impl_interner.rs b/compiler/rustc_middle/src/ty/context/impl_interner.rs index 1cce69353c14a..e73237bcfe50f 100644 --- a/compiler/rustc_middle/src/ty/context/impl_interner.rs +++ b/compiler/rustc_middle/src/ty/context/impl_interner.rs @@ -336,6 +336,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.assumptions_on_binders() } + fn dxf(self) -> bool { + self.dxf() + } + fn renormalize_rigid_aliases(self) -> bool { self.renormalize_rigid_aliases() } @@ -347,6 +351,61 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.coroutine_hidden_types(def_id) } + /// Hydrate a coroutine witness binder with NLL-derived assumptions. + /// For local def_ids, only call after borrowck has completed. + /// For remote (cross-crate) def_ids, safe to call at any time. + fn try_hydrate_coroutine_witness_scc( + self, + def_id: DefId, + args: ty::GenericArgsRef<'tcx>, + bound: ty::Binder<'tcx, ty::CoroutineWitnessTypes>>, + ) -> ty::Binder<'tcx, ty::CoroutineWitnessTypes>> { + if !self.sess.opts.unstable_opts.dxf { + return bound; + } + + // For local def_ids, the data was fed during mir_borrowck. + // For cross-crate def_ids, the data is decoded from metadata. + // Instantiate EarlyBinder with args, then skip the inner Binder + // (its bound vars match the witness binder in `bound`). + let nll_outlives = self + .coroutine_witness_scc_data(def_id) + .instantiate(self, args) + .skip_norm_wip() + .skip_binder(); + + if nll_outlives.assumptions.is_empty() { + return bound; + } + + let bound_vars = bound.bound_vars(); + let witness = bound.skip_binder(); + let new_assumptions: Vec<_> = + witness.assumptions.iter().chain(nll_outlives.assumptions.iter()).collect(); + let assumptions = self.mk_outlives(&new_assumptions); + ty::Binder::bind_with_vars( + ty::CoroutineWitnessTypes { types: witness.types, assumptions }, + bound_vars, + ) + } + + fn typeck_root_def_id(self, def_id: DefId) -> DefId { + self.typeck_root_def_id(def_id) + } + + fn ensure_mir_borrowck(self, typeck_root: DefId) -> bool { + if let Some(local_id) = typeck_root.as_local() { + let _ = self.mir_borrowck(local_id); + true + } else { + false + } + } + + fn mk_param_env(self, clauses: &[ty::Clause<'tcx>]) -> ty::ParamEnv<'tcx> { + ty::ParamEnv::new(self.mk_clauses(clauses)) + } + fn fn_sig(self, def_id: DefId) -> ty::EarlyBinder<'tcx, ty::PolyFnSig<'tcx>> { self.fn_sig(def_id) } diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index bfdb89dc409f6..1800b0ecc2206 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -523,7 +523,8 @@ impl<'tcx> GenericClauses<'tcx> { | ClauseKind::WellFormed(_) | ClauseKind::ConstEvaluatable(_) | ClauseKind::HostEffect(_) - | ClauseKind::UnstableFeature(_) => {} + | ClauseKind::UnstableFeature(_) + | ClauseKind::CoroutineWitnessRegionConstraints(..) => {} } clause.visit_with(&mut ParamChecker).is_continue() }) diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 923c48f2fddfa..fa8dad5d02d45 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -86,13 +86,13 @@ pub use self::opaque_types::OpaqueTypeKey; pub use self::pattern::{Pattern, PatternKind}; pub use self::predicate::{ AliasTerm, AliasTermKind, ArgOutlivesClause, Clause, ClauseKind, CoercePredicate, - ExistentialPredicate, ExistentialPredicateStableCmpExt, ExistentialProjection, - ExistentialTraitRef, HostEffectClause, NormalizesTo, OutlivesClause, PolyCoercePredicate, - PolyExistentialPredicate, PolyExistentialProjection, PolyExistentialTraitRef, - PolyProjectionPredicate, PolyRegionOutlivesClause, PolySubtypePredicate, PolyTraitPredicate, - PolyTraitRef, PolyTypeOutlivesClause, Predicate, PredicateKind, ProjectionPredicate, - RegionConstraint, RegionEqPredicate, RegionOutlivesClause, SubtypePredicate, TraitPredicate, - TraitRef, TypeOutlivesClause, + CoroutineRegionConstraints, ExistentialPredicate, ExistentialPredicateStableCmpExt, + ExistentialProjection, ExistentialTraitRef, HostEffectClause, NormalizesTo, OutlivesClause, + PolyCoercePredicate, PolyExistentialPredicate, PolyExistentialProjection, + PolyExistentialTraitRef, PolyProjectionPredicate, PolyRegionOutlivesClause, + PolySubtypePredicate, PolyTraitPredicate, PolyTraitRef, PolyTypeOutlivesClause, Predicate, + PredicateKind, ProjectionPredicate, RegionConstraint, RegionEqPredicate, RegionOutlivesClause, + SubtypePredicate, TraitPredicate, TraitRef, TypeOutlivesClause, }; pub use self::region::{ EarlyParamRegion, LateParamRegion, LateParamRegionKind, Region, RegionExt, RegionKind, @@ -1293,6 +1293,7 @@ impl<'tcx> TypingEnv<'tcx> { | TypingMode::Reflection | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => {} TypingMode::PostAnalysis | TypingMode::Codegen => return self, } @@ -1310,6 +1311,7 @@ impl<'tcx> TypingEnv<'tcx> { | TypingMode::Reflection | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis => {} TypingMode::Codegen => return self, @@ -2128,6 +2130,7 @@ impl<'tcx> TyCtxt<'tcx> { iter::repeat(source_info).take(CoroutineArgs::RESERVED_VARIANTS).collect(); let proxy_layout = CoroutineLayout { field_tys: [].into(), + reverse_local_map: IndexVec::new(), variant_fields, variant_source_info, storage_conflicts: BitMatrix::new(0, 0), diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index 990a424289e9b..91d5fce91494a 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -17,6 +17,7 @@ pub type ExistentialProjection<'tcx> = ir::ExistentialProjection>; pub type TraitPredicate<'tcx> = ir::TraitPredicate>; pub type HostEffectClause<'tcx> = ir::HostEffectClause>; pub type ClauseKind<'tcx> = ir::ClauseKind>; +pub type CoroutineRegionConstraints<'tcx> = ir::CoroutineRegionConstraints>; pub type PredicateKind<'tcx> = ir::PredicateKind>; pub type NormalizesTo<'tcx> = ir::NormalizesTo>; pub type CoercePredicate<'tcx> = ir::CoercePredicate>; diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 7fdfd8479b401..6d2c3f9a87dd6 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -3264,9 +3264,31 @@ define_print! { ty::ClauseKind::UnstableFeature(symbol) => { write!(p, "feature({symbol}) is enabled")?; } + ty::ClauseKind::CoroutineWitnessRegionConstraints(def_id, binder) => { + write!(p, "the coroutine witness `")?; + p.print_def_path(def_id, &[])?; + write!(p, "` has region constraints `")?; + binder.print(p)?; + write!(p, "`")?; + } + } + } + + &'tcx ty::List> { + let mut first = true; + for pred in self.iter() { + if !first { + p.write_str(", ")?; + } + first = false; + pred.print(p)?; } } + ty::CoroutineRegionConstraints<'tcx> { + self.0.print(p)?; + } + ty::PredicateKind<'tcx> { match *self { ty::PredicateKind::Clause(data) => data.print(p)?, diff --git a/compiler/rustc_mir_transform/src/coroutine/layout.rs b/compiler/rustc_mir_transform/src/coroutine/layout.rs index 030620fee57bf..10c800c38e3e9 100644 --- a/compiler/rustc_mir_transform/src/coroutine/layout.rs +++ b/compiler/rustc_mir_transform/src/coroutine/layout.rs @@ -432,8 +432,13 @@ pub(super) fn compute_layout<'tcx>( tys[saved_local].debuginfo_name.get_or_insert(var.name); } - let layout = - CoroutineLayout { field_tys: tys, variant_fields, variant_source_info, storage_conflicts }; + let layout = CoroutineLayout { + field_tys: tys, + reverse_local_map, + variant_fields, + variant_source_info, + storage_conflicts, + }; debug!(?remap); debug!(?layout); debug!(?storage_liveness); diff --git a/compiler/rustc_mir_transform/src/elaborate_drop.rs b/compiler/rustc_mir_transform/src/elaborate_drop.rs index 492759d666c83..7e2fb405e17b2 100644 --- a/compiler/rustc_mir_transform/src/elaborate_drop.rs +++ b/compiler/rustc_mir_transform/src/elaborate_drop.rs @@ -789,6 +789,7 @@ where | ty::TypingMode::Reflection | ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } => { bug!() } diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 040f98de7bcfd..d3d73f215154c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -101,7 +101,8 @@ where | ty::ClauseKind::WellFormed(..) | ty::ClauseKind::ConstEvaluatable(..) | ty::ClauseKind::HostEffect(..) - | ty::ClauseKind::UnstableFeature(..) => { + | ty::ClauseKind::UnstableFeature(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => { unreachable!("expected trait or projection predicate as an assumption") } }); @@ -489,6 +490,7 @@ where TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen @@ -1089,6 +1091,7 @@ where TypingMode::Typeck { .. } => self.opaques_with_sub_unified_hidden_type(self_ty), TypingMode::Coherence | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Reflection diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index ae78d68865de3..69e6506116775 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -86,12 +86,18 @@ where Ty::new_coroutine_witness_for_coroutine(ecx.cx(), def_id, args), ])), - ty::CoroutineWitness(def_id, args) => Ok(ecx - .cx() - .coroutine_hidden_types(def_id) - .instantiate(cx, args) - .skip_norm_wip() - .map_bound(|bound| bound.types.to_vec())), + ty::CoroutineWitness(def_id, args) => { + let bound = if should_hydrate_coroutine_witness(ecx, cx, def_id) { + cx.try_hydrate_coroutine_witness_scc( + def_id, + args, + ecx.cx().coroutine_hidden_types(def_id).instantiate(cx, args).skip_norm_wip(), + ) + } else { + ecx.cx().coroutine_hidden_types(def_id).instantiate(cx, args).skip_norm_wip() + }; + Ok(bound.map_bound(|witness| witness.types.to_vec())) + } ty::UnsafeBinder(bound_ty) => Ok(bound_ty.map_bound(|ty| vec![ty])), @@ -116,6 +122,35 @@ where } } +/// Determines whether the coroutine witness NLL facts is available for hydration. +/// +/// Returns `true` if `coroutine_witness_scc_data` is available for `def_id`. +/// For cross-crate coroutines, data comes from metadata. +/// For local coroutines, data is only available towards the end of the borrowck. +pub(in crate::solve) fn should_hydrate_coroutine_witness( + ecx: &EvalCtxt<'_, D>, + cx: I, + def_id: I::CoroutineId, +) -> bool +where + D: SolverDelegate, + I: Interner, +{ + // Only hydrate when DXF is active. + if !cx.dxf() { + return false; + } + + // Cross-crate: data comes from metadata, always available. + if !def_id.is_local() { + return true; + } + + // Local coroutines: SCC data is only available after borrowck. + // has_nll_inferred_bounds() returns true for PostBorrowck, PostAnalysis, Codegen. + ecx.typing_mode().has_nll_inferred_bounds() +} + #[instrument(level = "trace", skip(ecx), ret)] pub(in crate::solve) fn instantiate_constituent_tys_for_sizedness_trait( ecx: &EvalCtxt<'_, D>, diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index 68fc54605754d..37459932b36ec 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -5,14 +5,14 @@ use std::ops::ControlFlow; use rustc_macros::StableHash; use rustc_type_ir::data_structures::HashSet; use rustc_type_ir::inherent::*; -use rustc_type_ir::region_constraint::{RegionConstraint, evaluate_solver_constraint}; +use rustc_type_ir::region_constraint::{Assumptions, RegionConstraint, evaluate_solver_constraint}; use rustc_type_ir::relate::Relate; use rustc_type_ir::relate::solver_relating::RelateExt; use rustc_type_ir::search_graph::{CandidateHeadUsages, LowerAvailableDepth, PathKind}; use rustc_type_ir::solve::{ AccessedOpaques, ExternalRegionConstraints, FetchEligibleAssocItemResponse, MaybeInfo, NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunCondition, - RerunNonErased, RerunReason, RerunResultExt, SmallCopySet, + RerunNonErased, RerunReason, RerunResultExt, SmallCopySet, StalledOnCoroutines, }; use rustc_type_ir::{ self as ty, CanonicalVarValues, ClauseKind, InferCtxtLike, Interner, MayBeErased, @@ -158,6 +158,7 @@ where pub(super) opaque_accesses: AccessedOpaques, pub(super) inspect: inspect::EvaluationStepBuilder, + pub(super) coroutine_witness_universes: Vec, } #[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)] @@ -499,6 +500,7 @@ where origin_span, tainted: Ok(()), opaque_accesses: AccessedOpaques::default(), + coroutine_witness_universes: Vec::new(), }; let result = f(&mut ecx); assert!( @@ -564,6 +566,7 @@ where tainted: Ok(()), inspect: proof_tree_builder.new_evaluation_step(var_values), opaque_accesses: AccessedOpaques::default(), + coroutine_witness_universes: Vec::new(), }; let result = f(&mut ecx, input.goal); @@ -680,6 +683,7 @@ where TypingMode::Reflection | TypingMode::Coherence => true, TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::Codegen | TypingMode::PostAnalysis @@ -922,6 +926,11 @@ where ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(ct)) => { ecx.compute_const_evaluatable_goal(Goal { param_env, predicate: ct })? } + ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + .., + )) => { + panic!("CoroutineWitnessRegionConstraints should not be a goal") + } ty::PredicateKind::ConstEquate(_, _) => { panic!("ConstEquate should not be emitted when `-Znext-solver` is active") } @@ -1286,7 +1295,9 @@ where ) -> U { self.delegate.enter_forall_without_assumptions(value, |value| { let u = self.delegate.universe(); - let assumptions = if self.cx().assumptions_on_binders() { + let assumptions = if self.cx().assumptions_on_binders() + || !self.coroutine_witness_universes.is_empty() + { self.region_assumptions_for_placeholders_in_universe(value.clone(), u, param_env) } else { None @@ -1303,6 +1314,25 @@ where self.delegate.resolve_vars_if_possible(value) } + pub(super) fn universe(&self) -> ty::UniverseIndex { + self.delegate.universe() + } + + pub(super) fn get_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + ) -> Option> { + self.delegate.get_placeholder_assumptions(u) + } + + pub(super) fn insert_placeholder_assumptions( + &self, + u: ty::UniverseIndex, + assumptions: Option>, + ) { + self.delegate.insert_placeholder_assumptions(u, assumptions); + } + pub(super) fn shallow_resolve(&self, ty: I::Ty) -> I::Ty { self.delegate.shallow_resolve(ty) } @@ -1379,6 +1409,15 @@ where self.delegate.register_hidden_type_in_storage(opaque_type_key, hidden_ty, self.origin_span) } + /// Look up a previously registered hidden type for the given opaque type key. + /// Read-only — does not modify storage. + pub(super) fn lookup_hidden_type_in_storage( + &self, + opaque_type_key: &ty::OpaqueTypeKey, + ) -> Option { + self.delegate.lookup_hidden_type_in_storage(opaque_type_key) + } + pub(super) fn add_item_bounds_for_hidden_type( &mut self, opaque_def_id: I::OpaqueTyId, @@ -1525,20 +1564,46 @@ where previous call to `try_evaluate_added_goals!`" ); - let goals_certainty = match self.delegate.cx().assumptions_on_binders() { - true => { - let certainty = self.eagerly_handle_placeholders()?; - certainty.and(goals_certainty) + // Use eagerly_handle_placeholders when: + // 1. Global -Zassumptions-on-binders is active, OR + // 2. DXF is active and we entered universes for coroutine witness + // binders (which carry NLL-derived outlives assumptions that + // leak_check would ignore). + let use_eager_placeholders = self.delegate.cx().assumptions_on_binders() + || !self.coroutine_witness_universes.is_empty(); + + // During pre-borrowck, i.e. Typeck and BorrowckPendingScc, within + // coroutine witness universes, placeholder and leak failures may be due to + // missing NLL SCC data, not structural non-Send types. + // Convert to AMBIGUOUS so the goal gets stalled and re-evaluated in + // PostBorrowck against NLL data. + let is_pre_borrowck_witness = !self.coroutine_witness_universes.is_empty() + && !self.typing_mode().has_nll_inferred_bounds(); + + let goals_certainty = if use_eager_placeholders { + match self.eagerly_handle_placeholders() { + Ok(certainty) => certainty.and(goals_certainty), + Err(NoSolution) if is_pre_borrowck_witness => Certainty::Maybe(MaybeInfo { + cause: MaybeCause::Ambiguity, + opaque_types_jank: OpaqueTypesJank::AllGood, + stalled_on_coroutines: StalledOnCoroutines::Yes, + }), + Err(e) => return Err(e.into()), } - false => { - // We only check for leaks from universes which were entered inside - // of the query. - self.delegate.leak_check(self.max_input_universe).map_err(|NoSolution| { + } else { + // We only check for leaks from universes which were entered inside + // of the query. + match self.delegate.leak_check(self.max_input_universe) { + Ok(()) => goals_certainty, + Err(NoSolution) if is_pre_borrowck_witness => Certainty::Maybe(MaybeInfo { + cause: MaybeCause::Ambiguity, + opaque_types_jank: OpaqueTypesJank::AllGood, + stalled_on_coroutines: StalledOnCoroutines::Yes, + }), + Err(NoSolution) => { trace!("failed the leak check"); - NoSolution - })?; - - goals_certainty + return Err(NoSolution.into()); + } } }; @@ -1778,7 +1843,10 @@ fn should_rerun_after_erased_canonicalization( RerunCondition::OpaqueInStorage(defids), TypingMode::PostBorrowck { defined_opaque_types: opaques } | TypingMode::Typeck { defining_opaque_types_and_generators: opaques } - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: opaques }, + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: opaques, .. } + | TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: opaques, .. + }, ) => opaque_in_storage(opaques, defids), // ============================= (RerunCondition::AnyOpaqueHasInferAsHidden, TypingMode::Typeck { .. }) => { @@ -1790,7 +1858,8 @@ fn should_rerun_after_erased_canonicalization( | TypingMode::PostAnalysis | TypingMode::Codegen | TypingMode::Reflection - | TypingMode::PostTypeckUntilBorrowck { .. }, + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. }, ) => RerunDecision::No, // ============================= ( @@ -1812,7 +1881,10 @@ fn should_rerun_after_erased_canonicalization( ( RerunCondition::OpaqueInStorageOrAnyOpaqueHasInferAsHidden(defids), TypingMode::PostBorrowck { defined_opaque_types: opaques } - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: opaques }, + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: opaques, .. } + | TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: opaques, .. + }, ) => opaque_in_storage(opaques, defids), } } diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs index d9d18bdeea7e7..b9f8035eff35d 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs @@ -89,6 +89,7 @@ where tainted: outer.tainted, inspect: outer.inspect.take_and_enter_probe(), opaque_accesses: AccessedOpaques::default(), + coroutine_witness_universes: outer.coroutine_witness_universes.clone(), }; let r = nested.delegate.probe(|| { let r = f(&mut nested); diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs index 583dd391dd4d0..25249ae27c3f2 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/solver_region_constraints.rs @@ -36,7 +36,7 @@ where u: UniverseIndex, param_env: I::ParamEnv, ) -> Option> { - assert!(self.cx().assumptions_on_binders()); + assert!(self.cx().assumptions_on_binders() || !self.coroutine_witness_universes.is_empty()); struct RawAssumptions<'a, 'b, D: SolverDelegate, I: Interner> { ecx: &'a mut EvalCtxt<'b, D, I>, @@ -107,6 +107,17 @@ where reqs.into_iter().filter_map(|goal| goal.predicate.as_clause()), ); + // Also extract RegionOutlives from the param_env's caller_bounds. + // These include NLL-derived outlives injected for DXF coroutine witnesses. + let param_env_clauses: Vec<_> = param_env + .caller_bounds() + .iter() + .filter(|clause| { + matches!(clause.kind().skip_binder(), RegionOutlives(_)) + && max_universe(&**self.delegate, *clause) == u + }) + .collect(); + clauses.filter(move |clause| max_universe(&**self.delegate, *clause) == u).for_each( |clause| match clause.kind().skip_binder() { RegionOutlives(OutlivesClause(r1, r2)) => { @@ -120,6 +131,12 @@ where }, ); + for clause in param_env_clauses { + if let RegionOutlives(OutlivesClause(r1, r2)) = clause.kind().skip_binder() { + region_outlives_builder.add(r1, r2); + } + } + Some(Assumptions::new(type_outlives, region_outlives_builder.freeze())) } diff --git a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs index 6a8233754c6eb..af922eba46199 100644 --- a/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs +++ b/compiler/rustc_next_trait_solver/src/solve/normalizes_to.rs @@ -345,6 +345,7 @@ where // Outside of coherence, we treat the associated item as rigid instead. ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::PostAnalysis | ty::TypingMode::Reflection diff --git a/compiler/rustc_next_trait_solver/src/solve/project_goals/opaque_types.rs b/compiler/rustc_next_trait_solver/src/solve/project_goals/opaque_types.rs index 3387c8599b911..b5104d82a179c 100644 --- a/compiler/rustc_next_trait_solver/src/solve/project_goals/opaque_types.rs +++ b/compiler/rustc_next_trait_solver/src/solve/project_goals/opaque_types.rs @@ -42,7 +42,11 @@ where .map_err(Into::into) } TypingMode::Typeck { defining_opaque_types_and_generators: defining_opaque_types } - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } => { + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, .. } + | TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: defining_opaque_types, + .. + } => { let Some(def_id) = def_id .as_local() .filter(|&def_id| defining_opaque_types.contains(&def_id.into())) @@ -89,7 +93,8 @@ where // computed in HIR typeck as the initial value. match self.typing_mode().assert_not_erased() { TypingMode::Typeck { .. } => {} - TypingMode::PostTypeckUntilBorrowck { .. } => { + TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } => { let actual = cx .type_of_opaque_hir_typeck(def_id) .instantiate(cx, opaque_ty.args); @@ -135,17 +140,36 @@ where .map_err(Into::into); }; - let actual = cx.type_of(def_id.into()).instantiate(cx, opaque_ty.args); - // FIXME: Actually use a proper binder here instead of relying on `ReErased`. - // - // This is also probably unsound or sth :shrug: - let actual = actual.map(|v| { - fold_regions(cx, v, |re, _dbi| match re.kind() { - ty::ReErased => self.next_region_var(), - _ => re, - }) - }); - let actual = self.normalize(GoalSource::Misc, goal.param_env, actual)?; + // First check if the opaque type was pre-populated in storage by + // resolve_deferred_coroutine_goals. + // This avoids query cycle via type_of inside mir_borrowck. + let normalized_args = + cx.mk_args_from_iter(opaque_ty.args.iter().map(|arg| match arg.kind() { + ty::GenericArgKind::Lifetime(lt) => Ok(lt.into()), + ty::GenericArgKind::Type(ty) => { + self.structurally_normalize_ty(goal.param_env, ty).map(Into::into) + } + ty::GenericArgKind::Const(ct) => { + self.structurally_normalize_const(goal.param_env, ct).map(Into::into) + } + }))?; + let opaque_type_key = ty::OpaqueTypeKey { def_id, args: normalized_args }; + let actual = + if let Some(stored) = self.lookup_hidden_type_in_storage(&opaque_type_key) { + stored + } else { + let actual = cx.type_of(def_id.into()).instantiate(cx, opaque_ty.args); + // FIXME: Actually use a proper binder here instead of relying on `ReErased`. + // + // This is also probably unsound or sth :shrug: + let actual = actual.map(|v| { + fold_regions(cx, v, |re, _dbi| match re.kind() { + ty::ReErased => self.next_region_var(), + _ => re, + }) + }); + self.normalize(GoalSource::Misc, goal.param_env, actual)? + }; self.eq(goal.param_env, expected, actual)?; self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) .map_err(Into::into) diff --git a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs index ac9d6ca02893c..a8e9ab4af36c7 100644 --- a/compiler/rustc_next_trait_solver/src/solve/search_graph.rs +++ b/compiler/rustc_next_trait_solver/src/solve/search_graph.rs @@ -70,6 +70,7 @@ where TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index 082fe1b9bce63..925e979e3f127 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -1,18 +1,23 @@ //! Dealing with trait goals, i.e. `T: Trait<'a, U>`. +#[cfg(feature = "nightly")] +use rustc_data_structures::transitive_relation::TransitiveRelationBuilder; use rustc_type_ir::data_structures::IndexSet; use rustc_type_ir::fast_reject::DeepRejectCtxt; use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::SolverTraitLangItem; +use rustc_type_ir::region_constraint::Assumptions; +#[cfg(not(feature = "nightly"))] +use rustc_type_ir::region_constraint::TransitiveRelationBuilder; use rustc_type_ir::solve::{ AliasBoundKind, CandidatePreferenceMode, CanonicalResponse, MaybeInfo, NoSolutionOrRerunNonErased, OpaqueTypesJank, QueryResultOrRerunNonErased, RerunNonErased, RerunReason, RerunResultExt, SizedTraitKind, }; use rustc_type_ir::{ - self as ty, ExistentialPredicate, FieldInfo, Interner, MayBeErased, Movability, - PredicatePolarity, Region, TraitPredicate, TraitRef, TypeVisitableExt as _, TypingMode, - Unnormalized, Upcast as _, elaborate, + self as ty, CoroutineRegionConstraints, ExistentialPredicate, FieldInfo, Interner, MayBeErased, + Movability, PredicatePolarity, Region, TraitPredicate, TraitRef, TypeVisitableExt as _, + TypingMode, Unnormalized, Upcast as _, elaborate, }; use tracing::{debug, instrument, trace, warn}; @@ -268,6 +273,31 @@ where return cand; } + // In erased mode, CoroutineWitness auto-trait goals need rerun_always + // so they get re-evaluated in the original typing mode where + // NLL hydration kicks in. + // Only needed when NLL hydration and AoB are both active. + if ecx.cx().dxf() + && ecx.cx().assumptions_on_binders() + && matches!(goal.predicate.self_ty().kind(), ty::CoroutineWitness(..)) + { + match ecx.typing_mode() { + TypingMode::ErasedNotCoherence(MayBeErased) => { + return match ecx.opaque_accesses.rerun_always(RerunReason::TryStallCoroutine) { + Err(e) => Err(e.into()), + }; + } + TypingMode::Typeck { .. } + | TypingMode::Coherence + | TypingMode::Reflection + | TypingMode::PostAnalysis + | TypingMode::Codegen + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } + | TypingMode::PostBorrowck { .. } => {} + } + } + ecx.probe_and_evaluate_goal_for_constituent_tys( CandidateSource::BuiltinImpl(BuiltinImplSource::Misc), goal, @@ -1412,20 +1442,230 @@ where ) -> Result>, NoSolution>, ) -> Result, NoSolutionOrRerunNonErased> { self.probe_trait_candidate(source).enter(|ecx| { - let goals = ecx.enter_forall_with_assumptions( - constituent_tys(ecx, goal.predicate.self_ty())?, + let tys_binder = constituent_tys(ecx, goal.predicate.self_ty())?; + let goals = ecx.enter_forall_for_constituent_tys(goal, tys_binder); + ecx.add_goals(GoalSource::ImplWhereBound, goals)?; + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + }) + } + + /// Enter a forall scope for constituent type goals, optionally applying + /// NLL-derived coroutine witness region constraints from the ParamEnv. + /// + /// When checking `CoroutineWitness: Send`, the ParamEnv may contain a + /// `CoroutineWitnessRegionConstraints` clause with NLL-derived outlives + /// edges. This function merges those constraints into the forall's + /// placeholder assumptions so the solver can prove region obligations + /// that would otherwise fail. + fn enter_forall_for_constituent_tys( + &mut self, + goal: Goal>, + tys_binder: ty::Binder>, + ) -> Vec> { + let nll_constraints = self.lookup_coroutine_witness_constraints(goal); + let is_coroutine_witness = + matches!(goal.predicate.self_ty().kind(), ty::CoroutineWitness(..)); + let dxf = self.cx().dxf(); + + if let Some(constraints) = nll_constraints { + assert_eq!(tys_binder.bound_vars().len(), constraints.0.bound_vars().len()); + + let bound_vars = tys_binder.bound_vars(); + let hydrated_binder = ty::Binder::bind_with_vars( + (tys_binder.skip_binder(), constraints.0.skip_binder()), + bound_vars, + ); + + self.enter_forall_with_assumptions( + hydrated_binder, goal.param_env, - |ecx, tys| { + |ecx, (tys, assumptions)| { + if dxf { + let u = ecx.universe(); + ecx.coroutine_witness_universes.push(u); + } + ecx.register_nll_assumptions(assumptions); tys.into_iter() .map(|ty| { goal.with(ecx.cx(), goal.predicate.with_replaced_self_ty(ecx.cx(), ty)) }) .collect::>() }, - ); - ecx.add_goals(GoalSource::ImplWhereBound, goals)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }) + ) + } else if dxf && is_coroutine_witness { + let coro_def_id = match goal.predicate.self_ty().kind() { + ty::CoroutineWitness(def_id, _) => def_id, + _ => unreachable!(), + }; + // Only hydrate for cross-crate or PostBorrowck mode. + // All local coroutine auto-trait goals are stalled during typeck and re-evaluated + // during resolve_deferred_coroutine_goals in PostBorrowck mode. + if !super::assembly::structural_traits::should_hydrate_coroutine_witness( + self, + self.cx(), + coro_def_id, + ) { + return self.enter_forall_with_assumptions( + tys_binder, + goal.param_env, + |ecx, tys| { + let u = ecx.universe(); + ecx.coroutine_witness_universes.push(u); + tys.into_iter() + .map(|ty| { + goal.with( + ecx.cx(), + goal.predicate.with_replaced_self_ty(ecx.cx(), ty), + ) + }) + .collect::>() + }, + ); + } + let cx = self.cx(); + let coro_args = match goal.predicate.self_ty().kind() { + ty::CoroutineWitness(_, args) => args, + _ => unreachable!(), + }; + let assumptions = cx + .try_hydrate_coroutine_witness_scc( + coro_def_id, + coro_args, + cx.coroutine_hidden_types(coro_def_id) + .instantiate(cx, coro_args) + .skip_norm_wip(), + ) + .skip_binder() + .assumptions; + + if assumptions.is_empty() { + self.enter_forall_with_assumptions(tys_binder, goal.param_env, |ecx, tys| { + let u = ecx.universe(); + ecx.coroutine_witness_universes.push(u); + tys.into_iter() + .map(|ty| { + goal.with(ecx.cx(), goal.predicate.with_replaced_self_ty(ecx.cx(), ty)) + }) + .collect::>() + }) + } else { + let bound_vars = tys_binder.bound_vars(); + let assumptions_rebind = tys_binder.rebind(assumptions); + let hydrated_binder = ty::Binder::bind_with_vars( + (tys_binder.skip_binder(), assumptions_rebind.skip_binder()), + bound_vars, + ); + self.enter_forall_with_assumptions( + hydrated_binder, + goal.param_env, + |ecx, (tys, nll_assumptions)| { + let u = ecx.universe(); + ecx.coroutine_witness_universes.push(u); + ecx.register_nll_assumptions(nll_assumptions.clone()); + // Enrich the param_env with NLL outlives now so they survive + // canonicalization of sub-goals. + let cx = ecx.cx(); + let mut clauses: Vec = + goal.param_env.caller_bounds().iter().collect(); + for pred in nll_assumptions.iter() { + let ty::OutlivesClause(sup, sub) = pred; + match sup.kind() { + ty::GenericArgKind::Lifetime(r) => { + clauses.push( + ty::Binder::dummy(ty::ClauseKind::RegionOutlives( + ty::OutlivesClause(r, sub), + )) + .upcast(cx), + ); + } + _ => {} + } + } + let enriched_param_env = cx.mk_param_env(&clauses); + tys.into_iter() + .map(|ty| { + Goal::new( + cx, + enriched_param_env, + goal.predicate.with_replaced_self_ty(cx, ty), + ) + }) + .collect() + }, + ) + } + } else { + self.enter_forall_with_assumptions(tys_binder, goal.param_env, |ecx, tys| { + tys.into_iter() + .map(|ty| { + goal.with(ecx.cx(), goal.predicate.with_replaced_self_ty(ecx.cx(), ty)) + }) + .collect() + }) + } + } + + /// Look up `CoroutineWitnessRegionConstraints` in the ParamEnv for + /// the current goal's self type (if it's a `CoroutineWitness`). + fn lookup_coroutine_witness_constraints( + &self, + goal: Goal>, + ) -> Option> { + let self_ty = goal.predicate.self_ty(); + let coro_def_id = match self_ty.kind() { + ty::CoroutineWitness(def_id, _) => def_id, + _ => return None, + }; + + for clause in goal.param_env.caller_bounds().iter() { + if let ty::ClauseKind::CoroutineWitnessRegionConstraints(def_id, constraints) = + clause.kind().skip_binder() + { + if def_id == coro_def_id.into() { + return Some(constraints); + } + } + } + None + } + + /// Register NLL-derived outlives assumptions as placeholder assumptions + /// at the current universe level. Merges with any existing assumptions. + fn register_nll_assumptions(&mut self, assumptions: I::RegionAssumptions) { + let u = self.universe(); + let mut type_outlives = Vec::new(); + let mut region_outlives_builder = TransitiveRelationBuilder::default(); + for pred in assumptions.iter() { + let ty::OutlivesClause(sup, sub) = pred; + match sup.kind() { + ty::GenericArgKind::Type(ty) => { + type_outlives.push(ty::Binder::dummy(ty::OutlivesClause(ty, sub))); + } + ty::GenericArgKind::Lifetime(r) => { + region_outlives_builder.add(r, sub); + } + ty::GenericArgKind::Const(_) => {} + } + } + let new_assumptions = Assumptions::new(type_outlives, region_outlives_builder.freeze()); + + let existing = self.get_placeholder_assumptions(u); + let merged = if let Some(existing) = existing { + let mut merged_type_outlives = existing.type_outlives; + merged_type_outlives.extend(new_assumptions.type_outlives); + + let mut merged_region_builder = TransitiveRelationBuilder::default(); + for (r1, r2) in existing.region_outlives.base_edges() { + merged_region_builder.add(r1, r2); + } + for (r1, r2) in new_assumptions.region_outlives.base_edges() { + merged_region_builder.add(r1, r2); + } + Assumptions::new(merged_type_outlives, merged_region_builder.freeze()) + } else { + new_assumptions + }; + self.insert_placeholder_assumptions(u, Some(merged)); } } @@ -1656,11 +1896,23 @@ where }, ); } + TypingMode::BorrowckPendingScc { .. } => { + // In BorrowckPendingScc mode, NLL facts is not yet available. + // So stall local coroutine auto-trait goals as pending obligations. + // They will be re-evaluated towards the end of the borrowck. + if def_id.as_local().is_some() { + return Some(self.forced_ambiguity(MaybeInfo { + cause: MaybeCause::Ambiguity, + opaque_types_jank: OpaqueTypesJank::AllGood, + stalled_on_coroutines: StalledOnCoroutines::Yes, + })); + } + } TypingMode::Coherence - | TypingMode::PostAnalysis | TypingMode::Reflection + | TypingMode::PostAnalysis | TypingMode::Codegen - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _, .. } | TypingMode::PostBorrowck { defined_opaque_types: _ } => {} } } diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs index 879b239047fdc..9b4268df9334f 100644 --- a/compiler/rustc_privacy/src/lib.rs +++ b/compiler/rustc_privacy/src/lib.rs @@ -153,6 +153,7 @@ where ty::ClauseKind::ConstEvaluatable(ct) => ct.visit_with(self), ty::ClauseKind::WellFormed(term) => term.visit_with(self), ty::ClauseKind::UnstableFeature(_) => V::Result::output(), + ty::ClauseKind::CoroutineWitnessRegionConstraints(_, binder) => binder.visit_with(self), } } diff --git a/compiler/rustc_public/src/unstable/convert/stable/ty.rs b/compiler/rustc_public/src/unstable/convert/stable/ty.rs index 54e11aca7da77..ad2ca33aeddce 100644 --- a/compiler/rustc_public/src/unstable/convert/stable/ty.rs +++ b/compiler/rustc_public/src/unstable/convert/stable/ty.rs @@ -797,6 +797,9 @@ impl<'tcx> Stable<'tcx> for ty::ClauseKind<'tcx> { ClauseKind::UnstableFeature(_) => { unimplemented!() } + ClauseKind::CoroutineWitnessRegionConstraints(..) => { + unimplemented!() + } } } } diff --git a/compiler/rustc_query_impl/src/handle_cycle_error.rs b/compiler/rustc_query_impl/src/handle_cycle_error.rs index 6bc7bfc59b08b..ad96e814deaec 100644 --- a/compiler/rustc_query_impl/src/handle_cycle_error.rs +++ b/compiler/rustc_query_impl/src/handle_cycle_error.rs @@ -429,3 +429,12 @@ pub(crate) fn create_cycle_error<'tcx>( }) } } + +pub(crate) fn mir_borrowck<'tcx>( + _tcx: TyCtxt<'tcx>, + _key: LocalDefId, + _: Cycle<'tcx>, + err: Diag<'_>, +) -> Result<&'tcx rustc_middle::mir::BorrowCheckResult<'tcx>, ErrorGuaranteed> { + Err(err.delay_as_bug()) +} diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs index b3023c731eeff..dbbc7b8e7752a 100644 --- a/compiler/rustc_session/src/options.rs +++ b/compiler/rustc_session/src/options.rs @@ -2442,6 +2442,8 @@ options! { "do not treat all aliases in the environment as rigid with `-Znext-solver`"), dual_proc_macros: bool = (false, parse_bool, [TRACKED], "load proc macros for both target and host, but only link to the target (default: no)"), + dxf: bool = (false, parse_bool, [TRACKED], + "merge coroutine witness bound variables using NLL SCC data (default: no)"), dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ (default: no)"), diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 1897ed8fc84eb..781c203bbb3e3 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -730,7 +730,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { | ty::PredicateKind::Ambiguous | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature { .. }) | ty::PredicateKind::NormalizesTo { .. } - | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType { .. }) => { + | ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType { .. }) + | ty::PredicateKind::Clause( + ty::ClauseKind::CoroutineWitnessRegionConstraints { .. }, + ) => { span_bug!( span, "Unexpected `Predicate` for `SelectionError`: `{:?}`", diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index 4f8bac36abb6f..215acddb58648 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -430,6 +430,7 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => false, TypingMode::PostAnalysis | TypingMode::Codegen => { let poly_trait_ref = self.resolve_vars_if_possible(goal_trait_ref); diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index da596fe3b44b0..c48bb054894dc 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -13,7 +13,7 @@ use rustc_next_trait_solver::solve::{ StalledOnCoroutines, }; use thin_vec::ThinVec; -use tracing::instrument; +use tracing::{debug, instrument}; use self::derive_errors::*; use super::Certainty; @@ -188,7 +188,8 @@ where // the other case. TraitErrors::NoErrors } else { - TraitErrors::HasErrors(collect_remaining_errors_impl(self, infcx)) + let errors = collect_remaining_errors_impl(self, infcx); + if errors.is_empty() { TraitErrors::NoErrors } else { TraitErrors::HasErrors(errors) } } } @@ -356,6 +357,7 @@ where }) } + #[instrument(level = "debug", skip(self, infcx))] fn drain_stalled_obligations_for_coroutines( &mut self, infcx: &InferCtxt<'tcx>, @@ -365,19 +367,24 @@ where defining_opaque_types_and_generators } TypingMode::Coherence - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _, .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { defined_opaque_types: _ } | TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => return Default::default(), }; - if stalled_coroutines.is_empty() { + let dxf = infcx.tcx.dxf(); + debug!(?stalled_coroutines, dxf); + + if stalled_coroutines.is_empty() && !dxf { return Default::default(); } - self.obligations - .drain_pending(|_, stalled_on| { + let drained = self + .obligations + .drain_pending(|_obligation, stalled_on| { stalled_on.as_ref().is_some_and(|s| match s.stalled_certainty { Certainty::Maybe(MaybeInfo { cause: _, @@ -389,7 +396,9 @@ where }) .into_iter() .map(|(o, _)| o) - .collect() + .collect(); + debug!(?drained); + drained } } @@ -402,9 +411,21 @@ fn collect_remaining_errors_impl<'tcx, E>( where E: FromSolverError<'tcx, NextSolverError<'tcx>>, { - cx.obligations - .pending - .drain(..) + let pending = mem::take(&mut cx.obligations.pending); + + pending + .into_iter() + .filter(|(_, stalled_on)| { + !stalled_on.as_ref().is_some_and(|stalled_on| { + matches!( + stalled_on.stalled_certainty, + Certainty::Maybe(MaybeInfo { + stalled_on_coroutines: StalledOnCoroutines::Yes, + .. + }) + ) + }) + }) .map(|(obligation, _)| NextSolverError::Ambiguity(obligation)) .chain( cx.obligations diff --git a/compiler/rustc_trait_selection/src/traits/auto_trait.rs b/compiler/rustc_trait_selection/src/traits/auto_trait.rs index c885406f6dcfb..8511f65caac9b 100644 --- a/compiler/rustc_trait_selection/src/traits/auto_trait.rs +++ b/compiler/rustc_trait_selection/src/traits/auto_trait.rs @@ -894,7 +894,10 @@ impl<'tcx> AutoTraitFinder<'tcx> { | ty::PredicateKind::DynCompatible(..) | ty::PredicateKind::Subtype(..) | ty::PredicateKind::Coerce(..) - | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) => {} + | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + .., + )) => {} ty::PredicateKind::Ambiguous => return false, // FIXME(generic_const_exprs): you can absolutely add this as a where clauses diff --git a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs index 6d86a2cce6400..32bf2b1488fc8 100644 --- a/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs +++ b/compiler/rustc_trait_selection/src/traits/dyn_compatibility.rs @@ -272,7 +272,8 @@ fn predicate_references_self<'tcx>( | ty::ClauseKind::TypeOutlives(..) | ty::ClauseKind::RegionOutlives(..) | ty::ClauseKind::HostEffect(..) - | ty::ClauseKind::UnstableFeature(_) => None, + | ty::ClauseKind::UnstableFeature(_) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => None, // FIXME(generic_const_exprs): this can mention `Self` ty::ClauseKind::ConstEvaluatable(..) => None, @@ -338,7 +339,8 @@ fn generics_require_sized_self(tcx: TyCtxt<'_>, def_id: DefId) -> bool { | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(_) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::HostEffect(..) => false, + | ty::ClauseKind::HostEffect(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => false, }) } diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 3b111ab31575e..b17b5e33b8af6 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -175,8 +175,9 @@ where defining_opaque_types_and_generators } TypingMode::Coherence - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } - | TypingMode::PostBorrowck { defined_opaque_types: _ } + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _, .. } + | TypingMode::BorrowckPendingScc { .. } + | TypingMode::PostBorrowck { defined_opaque_types: _, .. } | TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => return Default::default(), @@ -464,6 +465,9 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) => { unreachable!("unexpected higher ranked `UnstableFeature` goal") } + ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints(..)) => { + unreachable!("unexpected higher ranked `CoroutineWitnessRegionConstraints` goal") + } }, Some(pred) => match pred { ty::PredicateKind::Clause(ty::ClauseKind::Trait(data)) => { @@ -840,6 +844,13 @@ impl<'a, 'tcx> ObligationProcessor for FulfillProcessor<'a, 'tcx> { ProcessResult::Unchanged } } + ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + .., + )) => { + bug!( + "CoroutineWitnessRegionConstraints should not be evaluated in old fulfillment" + ) + } }, } } diff --git a/compiler/rustc_trait_selection/src/traits/normalize.rs b/compiler/rustc_trait_selection/src/traits/normalize.rs index f00b300c7e971..aa5ab25b95e68 100644 --- a/compiler/rustc_trait_selection/src/traits/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/normalize.rs @@ -142,6 +142,7 @@ pub(super) fn needs_normalization<'tcx, T: TypeVisitable>>( TypingMode::Coherence | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => flags.remove(ty::TypeFlags::HAS_TY_OPAQUE), TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => {} } @@ -431,6 +432,7 @@ impl<'a, 'b, 'tcx> TypeFolder> for AssocTypeNormalizer<'a, 'b, 'tcx TypingMode::Coherence | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => ty.super_fold_with(self), TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => { let recursion_limit = self.cx().recursion_limit(); diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 7da8c68ff894a..1db8d53d499cd 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -985,6 +985,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => { debug!( assoc_ty = ?selcx.tcx().def_path_str(node_item.item.def_id), diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs index 489e4f7a93d53..312e661072b11 100644 --- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs +++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs @@ -217,6 +217,7 @@ impl<'a, 'tcx> FallibleTypeFolder> for QueryNormalizer<'a, 'tcx> { TypingMode::Coherence | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } => ty.try_super_fold_with(self)?, TypingMode::Reflection | TypingMode::PostAnalysis | TypingMode::Codegen => { diff --git a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs index 81cf4ac607074..111891daf7106 100644 --- a/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs +++ b/compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs @@ -127,7 +127,10 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>( | ty::PredicateKind::ConstEquate(..) | ty::PredicateKind::Ambiguous | ty::PredicateKind::NormalizesTo(..) - | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) => {} + | ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_)) + | ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + .., + )) => {} // We need to search through *all* WellFormed predicates ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(term)) => { diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 9b4ee13bf1b63..9d0da2b6ef1a7 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -965,6 +965,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { ty::PredicateKind::NormalizesTo(..) => { bug!("NormalizesTo is only used by the new solver") } + ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + .., + )) => { + bug!( + "CoroutineWitnessRegionConstraints should not be evaluated as a goal in select" + ) + } ty::PredicateKind::Ambiguous => Ok(EvaluatedToAmbig), ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(ct, ty)) => { let ct = self.infcx.shallow_resolve_const(ct); @@ -1475,6 +1482,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen => return Ok(()), @@ -1522,7 +1530,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // This is likely fixed by better caching in general in the new solver. // See: . TypingMode::Typeck { defining_opaque_types_and_generators: defining_opaque_types } - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } => { + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, .. } + | TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: defining_opaque_types, + .. + } => { defining_opaque_types.is_empty() || (!pred.has_opaque_types() && !pred.has_coroutines()) } @@ -2565,6 +2577,7 @@ impl<'tcx> SelectionContext<'_, 'tcx> { TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::Codegen | TypingMode::ErasedNotCoherence(_) @@ -2915,11 +2928,15 @@ impl<'tcx> SelectionContext<'_, 'tcx> { TypingMode::Typeck { defining_opaque_types_and_generators: stalled_generators } => { def_id.as_local().is_some_and(|def_id| stalled_generators.contains(&def_id)) } + TypingMode::BorrowckPendingScc { .. } => { + // Stall all local coroutines here. + def_id.as_local().is_some() + } TypingMode::Coherence | TypingMode::PostAnalysis | TypingMode::Reflection | TypingMode::Codegen - | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _ } + | TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: _, .. } | TypingMode::PostBorrowck { defined_opaque_types: _ } => false, } } diff --git a/compiler/rustc_trait_selection/src/traits/util.rs b/compiler/rustc_trait_selection/src/traits/util.rs index e611b4fc6b6ac..4547c51685f18 100644 --- a/compiler/rustc_trait_selection/src/traits/util.rs +++ b/compiler/rustc_trait_selection/src/traits/util.rs @@ -81,7 +81,8 @@ pub fn expand_trait_aliases<'tcx>( | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::ConstEvaluatable(_) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::HostEffect(..) => {} + | ty::ClauseKind::HostEffect(..) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => {} } } diff --git a/compiler/rustc_trait_selection/src/traits/wf.rs b/compiler/rustc_trait_selection/src/traits/wf.rs index 41d2d9adfea74..7371f240c50b4 100644 --- a/compiler/rustc_trait_selection/src/traits/wf.rs +++ b/compiler/rustc_trait_selection/src/traits/wf.rs @@ -197,6 +197,7 @@ pub fn clause_obligations<'tcx>( wf.add_wf_preds_for_term(ct.into()); } ty::ClauseKind::UnstableFeature(_) => {} + ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => {} } wf.normalize(infcx) @@ -1265,7 +1266,8 @@ pub fn object_region_bounds<'tcx>( | ty::ClauseKind::ConstArgHasType(_, _) | ty::ClauseKind::WellFormed(_) | ty::ClauseKind::UnstableFeature(_) - | ty::ClauseKind::ConstEvaluatable(_) => None, + | ty::ClauseKind::ConstEvaluatable(_) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => None, } }) .collect() diff --git a/compiler/rustc_traits/src/normalize_erasing_regions.rs b/compiler/rustc_traits/src/normalize_erasing_regions.rs index 1ba385e86b310..14dfde3b05b69 100644 --- a/compiler/rustc_traits/src/normalize_erasing_regions.rs +++ b/compiler/rustc_traits/src/normalize_erasing_regions.rs @@ -75,6 +75,7 @@ fn not_outlives_predicate(p: ty::Predicate<'_>) -> bool { | ty::PredicateKind::Subtype(..) | ty::PredicateKind::Coerce(..) | ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..)) + | ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints(..)) | ty::PredicateKind::ConstEquate(..) | ty::PredicateKind::Ambiguous => true, } diff --git a/compiler/rustc_ty_utils/src/instance.rs b/compiler/rustc_ty_utils/src/instance.rs index c0f476a7feaca..838a8f1327cad 100644 --- a/compiler/rustc_ty_utils/src/instance.rs +++ b/compiler/rustc_ty_utils/src/instance.rs @@ -168,6 +168,7 @@ fn resolve_associated_item<'tcx>( | ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } | ty::TypingMode::Reflection + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } => false, ty::TypingMode::PostAnalysis | ty::TypingMode::Codegen => { !trait_ref.still_further_specializable() diff --git a/compiler/rustc_ty_utils/src/layout.rs b/compiler/rustc_ty_utils/src/layout.rs index 9e9aed008bf31..101ffd2581de5 100644 --- a/compiler/rustc_ty_utils/src/layout.rs +++ b/compiler/rustc_ty_utils/src/layout.rs @@ -89,6 +89,7 @@ fn layout_of<'tcx>( ty::TypingMode::Coherence | ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::Reflection | ty::TypingMode::ErasedNotCoherence(_) @@ -555,6 +556,7 @@ fn layout_of_uncached<'tcx>( ty::TypingMode::Coherence | ty::TypingMode::Typeck { .. } | ty::TypingMode::PostTypeckUntilBorrowck { .. } + | ty::TypingMode::BorrowckPendingScc { .. } | ty::TypingMode::PostBorrowck { .. } | ty::TypingMode::Reflection | ty::TypingMode::ErasedNotCoherence(_) diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 828bd107f5b34..13ff07e0c5351 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -245,6 +245,9 @@ impl> Elaborator { ty::ClauseKind::UnstableFeature(_) => { // Nothing to elaborate } + ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => { + // Nothing to elaborate + } } } } diff --git a/compiler/rustc_type_ir/src/flags.rs b/compiler/rustc_type_ir/src/flags.rs index 2db0c83098b54..fb115d78d279b 100644 --- a/compiler/rustc_type_ir/src/flags.rs +++ b/compiler/rustc_type_ir/src/flags.rs @@ -441,6 +441,21 @@ impl FlagComputation { self.add_term(term); } ty::PredicateKind::Clause(ty::ClauseKind::UnstableFeature(_sym)) => {} + ty::PredicateKind::Clause(ty::ClauseKind::CoroutineWitnessRegionConstraints( + _def_id, + binder, + )) => { + self.bound_computation(binder.0, |computation, assumptions| { + for pred in assumptions.iter() { + match pred.0.kind() { + ty::GenericArgKind::Type(ty) => computation.add_ty(ty), + ty::GenericArgKind::Lifetime(lt) => computation.add_region(lt), + ty::GenericArgKind::Const(ct) => computation.add_const(ct), + } + computation.add_region(pred.1); + } + }); + } ty::PredicateKind::Ambiguous => {} } } diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index b84b029bc155f..3df91380553f8 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -119,7 +119,23 @@ pub enum TypingMode { /// This is currently only used by the new solver as it results in new /// non-universal defining uses of opaque types, which is a breaking change. /// See tests/ui/impl-trait/non-defining-use/as-projection-term.rs. - PostTypeckUntilBorrowck { defining_opaque_types: I::LocalDefIds }, + PostTypeckUntilBorrowck { defining_opaque_types: I::LocalDefIds, borrowck_root: I::LocalDefId }, + /// Used during `check_coroutine_obligations` when `-Zdxf` is active and + /// `mir_borrowck` hasn't completed yet. NLL SCC data is pending. + /// Coroutine auto-trait goals are stalled as pending obligations in + /// `try_stall_coroutine` rather than being evaluated eagerly. + /// They will be re-evaluated after `mir_borrowck` captures the facts and + /// feeds it to `try_hydrate_coroutine_witness_scc`. + /// + /// This behaves like `PostTypeckUntilBorrowck` for opaque type handling, + /// but additionally stalls coroutine auto-trait goals whose DefId is a + /// descendant of `borrowck_root`. + BorrowckPendingScc { + defining_opaque_types_and_generators: I::LocalDefIds, + /// The typeck root currently being borrow-checked. + /// Coroutines nested under this root have pending SCC data. + borrowck_root: I::LocalDefId, + }, /// Any analysis after borrowck for a given body should be able to use all the /// hidden types defined by borrowck, without being able to define any new ones. /// @@ -186,9 +202,19 @@ impl PartialEq for TypingModeEqWrapper { TypingMode::Typeck { defining_opaque_types_and_generators: r }, ) => l == r, ( - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: l }, - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: r }, - ) => l == r, + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: l, borrowck_root: lr }, + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types: r, borrowck_root: rr }, + ) => l == r && lr == rr, + ( + TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: l, + borrowck_root: lr, + }, + TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators: r, + borrowck_root: rr, + }, + ) => l == r && lr == rr, ( TypingMode::PostBorrowck { defined_opaque_types: l }, TypingMode::PostBorrowck { defined_opaque_types: r }, @@ -204,6 +230,7 @@ impl PartialEq for TypingModeEqWrapper { | TypingMode::Reflection | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen @@ -228,6 +255,7 @@ impl TypingMode { TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen @@ -246,6 +274,7 @@ impl TypingMode { TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Coherence + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen @@ -265,11 +294,42 @@ impl TypingMode { | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen => false, } } + + pub fn has_nll_inferred_bounds(&self) -> bool { + match self { + TypingMode::Coherence + | TypingMode::Typeck { .. } + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } + | TypingMode::ErasedNotCoherence(_) => false, + TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen => { + true + } + } + } + + /// Returns `true` if we are in `BorrowckPendingScc` mode, where + /// coroutine auto-trait obligations are expected to stall awaiting NLL SCC data. + pub fn is_borrowck_pending_scc(&self) -> bool { + match self { + TypingMode::BorrowckPendingScc { .. } => true, + TypingMode::Coherence + | TypingMode::Typeck { .. } + | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::Reflection + | TypingMode::PostBorrowck { .. } + | TypingMode::PostAnalysis + | TypingMode::Codegen + | TypingMode::ErasedNotCoherence(_) => false, + } + } } impl TypingMode { @@ -283,9 +343,16 @@ impl TypingMode { TypingMode::Typeck { defining_opaque_types_and_generators } => { TypingMode::Typeck { defining_opaque_types_and_generators } } - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } => { - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, borrowck_root } => { + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, borrowck_root } } + TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators, + borrowck_root, + } => TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators, + borrowck_root, + }, TypingMode::PostBorrowck { defined_opaque_types } => { TypingMode::PostBorrowck { defined_opaque_types } } @@ -328,10 +395,19 @@ impl TypingMode { if defining_opaque_types.is_empty() { TypingMode::non_body_analysis() } else { - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } + TypingMode::PostTypeckUntilBorrowck { + defining_opaque_types, + borrowck_root: body_def_id, + } } } + pub fn borrowck_pending_scc(cx: I, borrowck_root: I::LocalDefId) -> TypingMode { + let defining_opaque_types_and_generators = + cx.opaque_types_and_coroutines_defined_by(borrowck_root); + TypingMode::BorrowckPendingScc { defining_opaque_types_and_generators, borrowck_root } + } + pub fn post_borrowck_analysis(cx: I, body_def_id: I::LocalDefId) -> TypingMode { let defined_opaque_types = cx.opaque_types_defined_by(body_def_id); if defined_opaque_types.is_empty() { @@ -349,9 +425,16 @@ impl From> for TypingMode { TypingMode::Typeck { defining_opaque_types_and_generators } } - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } => { - TypingMode::PostTypeckUntilBorrowck { defining_opaque_types } + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, borrowck_root } => { + TypingMode::PostTypeckUntilBorrowck { defining_opaque_types, borrowck_root } } + TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators, + borrowck_root, + } => TypingMode::BorrowckPendingScc { + defining_opaque_types_and_generators, + borrowck_root, + }, TypingMode::PostBorrowck { defined_opaque_types } => { TypingMode::PostBorrowck { defined_opaque_types } } @@ -552,6 +635,14 @@ pub trait InferCtxtLike: Sized { hidden_ty: ::Ty, span: ::Span, ) -> Option<::Ty>; + + /// Look up a previously registered hidden type for the given opaque type key. + /// Read-only — does not modify storage. + fn lookup_hidden_type_in_storage( + &self, + opaque_type_key: &ty::OpaqueTypeKey, + ) -> Option<::Ty>; + fn add_duplicate_opaque_type( &self, opaque_type_key: ty::OpaqueTypeKey, @@ -600,6 +691,7 @@ where | TypingMode::Typeck { .. } | TypingMode::PostTypeckUntilBorrowck { .. } | TypingMode::Reflection + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis => infcx.cx().features().feature_bound_holds_in_crate(symbol), TypingMode::Codegen => true, diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 859996d67eb64..11ff8c31adc6c 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -480,6 +480,7 @@ pub trait Predicate>: | PredicateKind::Clause(ClauseKind::Projection(_)) | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) | PredicateKind::Clause(ClauseKind::UnstableFeature(_)) + | PredicateKind::Clause(ClauseKind::CoroutineWitnessRegionConstraints(..)) | PredicateKind::DynCompatible(_) | PredicateKind::Subtype(_) | PredicateKind::Coerce(_) diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index cfe5ead83cb01..d7549ea36884a 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -202,6 +202,9 @@ pub trait Interner: type Clause: Clause; type Clauses: Clauses; + /// Create a `ParamEnv` from a slice of clauses. + fn mk_param_env(self, clauses: &[Self::Clause]) -> Self::ParamEnv; + fn with_global_cache(self, f: impl FnOnce(&mut search_graph::GlobalCache) -> R) -> R; fn with_canonical_param_env_cache( @@ -285,6 +288,8 @@ pub trait Interner: fn assumptions_on_binders(self) -> bool; + fn dxf(self) -> bool; + fn renormalize_rigid_aliases(self) -> bool; fn coroutine_hidden_types( @@ -292,6 +297,33 @@ pub trait Interner: def_id: Self::CoroutineId, ) -> ty::EarlyBinder>>; + /// Try to merge coroutine witness bound variables using NLL SCC data. + /// Merges positions with the same SCC into a single bound variable, + /// and adds NLL-derived outlives edges to the assumptions. + /// The default implementation is a no-op. + /// + /// Do not call this function at any time before the borrowck has completed. + fn try_hydrate_coroutine_witness_scc( + self, + _: Self::CoroutineId, + _: Self::GenericArgs, + bound: ty::Binder>, + ) -> ty::Binder> { + bound + } + + /// Returns the typeck root of `def_id`. For nested bodies this walks up to + /// the outermost function. + /// For top-level items, returns `def_id` itself. + fn typeck_root_def_id(self, def_id: Self::DefId) -> Self::DefId; + + /// Ensure `mir_borrowck` has run for the typeck root, so that + /// feedable queries like `coroutine_witness_scc_data` are available. + /// Returns `true` if mir_borrowck completed successfully. + fn ensure_mir_borrowck(self, _typeck_root: Self::DefId) -> bool { + false + } + fn fn_sig( self, def_id: Self::FunctionId, diff --git a/compiler/rustc_type_ir/src/predicate_kind.rs b/compiler/rustc_type_ir/src/predicate_kind.rs index e57f0f1e0cd8d..d3448e081be47 100644 --- a/compiler/rustc_type_ir/src/predicate_kind.rs +++ b/compiler/rustc_type_ir/src/predicate_kind.rs @@ -5,8 +5,41 @@ use derive_where::derive_where; use rustc_macros::{Decodable_NoContext, Encodable_NoContext, StableHash_NoContext}; use rustc_type_ir_macros::{GenericTypeVisitable, TypeFoldable_Generic, TypeVisitable_Generic}; +use crate::visit::TypeVisitable; use crate::{self as ty, Interner, Region}; +#[derive_where(Clone, Copy, Hash, PartialEq, Eq, Debug; I: Interner)] +#[derive(TypeVisitable_Generic, GenericTypeVisitable, TypeFoldable_Generic)] +#[cfg_attr(feature = "nightly", derive(StableHash_NoContext))] +pub struct CoroutineRegionConstraints(pub ty::Binder); + +#[cfg(feature = "nightly")] +impl rustc_serialize::Encodable + for CoroutineRegionConstraints +where + I::RegionAssumptions: rustc_serialize::Encodable, + I::BoundVarKinds: rustc_serialize::Encodable, +{ + fn encode(&self, e: &mut E) { + self.0.bound_vars().encode(e); + self.0.as_ref().skip_binder().encode(e); + } +} + +#[cfg(feature = "nightly")] +impl rustc_serialize::Decodable + for CoroutineRegionConstraints +where + I::RegionAssumptions: TypeVisitable + rustc_serialize::Decodable, + I::BoundVarKinds: rustc_serialize::Decodable, +{ + fn decode(decoder: &mut D) -> Self { + let bound_vars = rustc_serialize::Decodable::decode(decoder); + let value = rustc_serialize::Decodable::decode(decoder); + CoroutineRegionConstraints(ty::Binder::bind_with_vars(value, bound_vars)) + } +} + /// A clause is something that can appear in where bounds or be inferred /// by implied bounds. #[derive_where(Clone, Copy, Hash, PartialEq; I: Interner)] @@ -53,6 +86,9 @@ pub enum ClauseKind { #[type_visitable(ignore)] I::Symbol, ), + + /// NLL-derived region constraints for a coroutine witness. + CoroutineWitnessRegionConstraints(I::DefId, CoroutineRegionConstraints), } impl Eq for ClauseKind {} @@ -123,6 +159,9 @@ impl fmt::Debug for ClauseKind { ClauseKind::UnstableFeature(feature_name) => { write!(f, "UnstableFeature({feature_name:?})") } + ClauseKind::CoroutineWitnessRegionConstraints(def_id, binder) => { + write!(f, "CoroutineWitnessRegionConstraints({def_id:?}, {binder:?})") + } } } } diff --git a/compiler/rustc_type_ir/src/region_constraint.rs b/compiler/rustc_type_ir/src/region_constraint.rs index d34a1ec52d153..b5b7b5aaf607f 100644 --- a/compiler/rustc_type_ir/src/region_constraint.rs +++ b/compiler/rustc_type_ir/src/region_constraint.rs @@ -642,6 +642,11 @@ fn pull_region_outlives_constraints_out_of_universe< constraint } RegionOutlives(region_1, region_2) => { + // Trivially true: any region outlives itself. + if region_1 == region_2 { + return RegionConstraint::new_true(); + } + let region_1_u = max_universe(infcx, region_1); let region_2_u = max_universe(infcx, region_2); @@ -654,6 +659,18 @@ fn pull_region_outlives_constraints_out_of_universe< None => return RegionConstraint::Ambiguity, }; + // When assumptions contain evidence that same-universe + // placeholders satisfy an outlives constraint, be it from NLL + // or WF-derived bounds, we can just resolve directly. + // Without this, constraints like `'!0_u1: '!1_u1` would fail + // because the lower-universe intermediary search below discards + // same-universe regions. + if region_1_u == u && region_2_u == u { + if regions_outlived_by(region_1, assumptions).any(|r| r == region_2) { + return RegionConstraint::new_true(); + } + } + let mut candidates = vec![]; for ub in regions_outlived_by(region_1, assumptions).filter(|r| max_universe(infcx, *r) < u) @@ -898,6 +915,7 @@ fn rewrite_type_outlives_constraints_in_universe_for_eager_placeholder_handling< TypingMode::Typeck { .. } | TypingMode::ErasedNotCoherence { .. } | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::Reflection | TypingMode::PostAnalysis diff --git a/compiler/rustc_type_ir/src/relate/combine.rs b/compiler/rustc_type_ir/src/relate/combine.rs index 4c0fe9cd25724..46bdc13c37a2d 100644 --- a/compiler/rustc_type_ir/src/relate/combine.rs +++ b/compiler/rustc_type_ir/src/relate/combine.rs @@ -130,6 +130,7 @@ where TypingMode::Typeck { .. } | TypingMode::Reflection | TypingMode::PostTypeckUntilBorrowck { .. } + | TypingMode::BorrowckPendingScc { .. } | TypingMode::PostBorrowck { .. } | TypingMode::PostAnalysis | TypingMode::Codegen => structurally_relate_tys(relation, a, b), diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index 343a93ba0e3f6..17594ee181aae 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -446,7 +446,8 @@ pub(crate) fn clean_clause<'tcx>( | ty::ClauseKind::ConstArgHasType(..) | ty::ClauseKind::UnstableFeature(..) // FIXME(const_trait_impl): We can probably use this `HostEffect` pred to render `~const`. - | ty::ClauseKind::HostEffect(_) => None, + | ty::ClauseKind::HostEffect(_) + | ty::ClauseKind::CoroutineWitnessRegionConstraints(..) => None, } } diff --git a/tests/ui/async-await/auxiliary/dxf-cross-crate-lib.rs b/tests/ui/async-await/auxiliary/dxf-cross-crate-lib.rs new file mode 100644 index 0000000000000..4a2146804ef51 --- /dev/null +++ b/tests/ui/async-await/auxiliary/dxf-cross-crate-lib.rs @@ -0,0 +1,25 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf + +#![allow(dead_code)] + +use std::marker::PhantomData; + +pub struct Guarded<'a, 'b> { + _p: PhantomData<(fn(&'a ()) -> &'a (), fn(&'b ()) -> &'b ())>, + _raw: *mut (), +} + +unsafe impl<'a, 'b: 'a> Send for Guarded<'a, 'b> {} + +pub fn make_guarded<'a>(_x: &mut &'a (), _y: &mut &'a ()) -> Guarded<'a, 'a> { + Guarded { _p: PhantomData, _raw: std::ptr::null_mut() } +} + +pub async fn use_guarded(data: &()) { + let mut r1 = data; + let mut r2 = data; + let g = make_guarded(&mut r1, &mut r2); + std::future::pending::<()>().await; + drop(g); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-assoc-type.dxf.stderr b/tests/ui/async-await/dxf-coroutine-send-assoc-type.dxf.stderr new file mode 100644 index 0000000000000..7d2a04822fba5 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-assoc-type.dxf.stderr @@ -0,0 +1,24 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-assoc-type.rs:73:5 + | +LL | assert_send(my_method(&mut s, &data)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-assoc-type.rs:73:5 + | +LL | assert_send(my_method(&mut s, &data)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-assoc-type.rs:73:5 + | +LL | assert_send(my_method(&mut s, &data)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 3 previous errors + diff --git a/tests/ui/async-await/dxf-coroutine-send-assoc-type.rs b/tests/ui/async-await/dxf-coroutine-send-assoc-type.rs new file mode 100644 index 0000000000000..46187afd809d2 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-assoc-type.rs @@ -0,0 +1,74 @@ +//@ edition: 2024 +//@ revisions: no_dxf dxf dxf_aob +//@[no_dxf] compile-flags: -Znext-solver -Zassumptions-on-binders +//@[no_dxf] check-pass +//@[dxf] compile-flags: -Znext-solver -Zdxf +//@[dxf] known-bug: #114046 +//@[dxf_aob] compile-flags: -Znext-solver -Zdxf -Zassumptions-on-binders +//@[dxf_aob] check-pass + +// Adapted from the pattern in issue #114046 (higher-ranked-auto-trait-13). +// Associated types create lifetime dependencies that make Send conditional +// on lifetime constraints from NLL SCC data. + +#![allow(dead_code)] +use std::marker::PhantomData; + +trait Callable<'a>: Send + Sync { + fn callable(data: &'a [u8]); +} + +trait Getter<'a>: Send + Sync { + type ItemSize: Send + Sync; + fn get(data: &'a [u8]); +} + +struct List<'a, A: Getter<'a>> { + data: &'a [u8], + item_size: A::ItemSize, + phantom: PhantomData, +} + +struct GetterImpl<'a, T: Callable<'a> + 'a> { + p: PhantomData<&'a T>, +} + +impl<'a, T: Callable<'a> + 'a> Getter<'a> for GetterImpl<'a, T> { + type ItemSize = (); + fn get(data: &'a [u8]) { + ::callable(data); + } +} + +struct Impl<'a> { + _data: &'a [u8], +} + +impl<'a> Callable<'a> for Impl<'a> { + fn callable(_: &'a [u8]) {} +} + +struct StructWithLifetime<'a> { + marker: &'a PhantomData, +} + +async fn yield_point() {} + +fn assert_send(_: impl Send) {} + +async fn my_method(s: &mut StructWithLifetime<'_>, data: &[u8]) { + let _named = List::<'_, GetterImpl>> { + data, + item_size: (), + phantom: PhantomData, + }; + yield_point().await; + drop(_named); +} + +fn main() { + let ph = PhantomData; + let mut s = StructWithLifetime { marker: &ph }; + let data = vec![1u8]; + assert_send(my_method(&mut s, &data)); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.rs b/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.rs new file mode 100644 index 0000000000000..c502a85480b84 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.rs @@ -0,0 +1,71 @@ +//@ revisions: without_aob with_aob +//@ edition: 2024 +//@ [without_aob] compile-flags: -Znext-solver -Zdxf +//@ [without_aob] known-bug: #126550 +//@ [with_aob] compile-flags: -Znext-solver -Zdxf -Zassumptions-on-binders +//@ [with_aob] check-pass + +// Minimized from a futures-util join_all/then/map pattern. +// +// The bug chain: +// 1. MIR erases free regions to `ReErased` as usual. +// 2. `coroutine_hidden_types` assigns each erased region a distinct `BoundVar`. +// 3. Auto-trait solver instantiates bound vars as placeholders. +// 4. Projection normalization on `MaybeDone::Done(...)` triggers a check on +// the `F: FnOnce(Fut::Output)` where-clause through the Flatten> +// chain, requiring `for<'a> FnOnce(&'a ())` but closure only implements +// `FnOnce(&'static ())`. +// +// Unlike #126551, the reference is in the closure's `async move { &THING }`. +// +// With `-Zassumptions-on-binders`, the solver can use the NLL data `'a: 'static` +// to prove the higher-ranked bound. + +#![allow(dead_code)] +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +const THING: () = (); + +struct Map(Fut, F); + +impl T, T> Future for Map { + type Output = T; + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { todo!() } +} + +enum Flatten { + First(Fut1), + Second(Fut2), +} + +impl Future for Flatten +where + Fut::Output: Future, +{ + type Output = ::Output; + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { todo!() } +} + +enum MaybeDone { + Future(Fut), + Done(Fut::Output), +} + +impl Future for MaybeDone { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { todo!() } +} + +async fn foo() { + MaybeDone::Future(Flatten::First(Map(async {}, |()| async move { &THING }))).await; +} + +fn trouble() -> impl Send { + foo() +} + +fn main() { + trouble(); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.without_aob.stderr b/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.without_aob.stderr new file mode 100644 index 0000000000000..44248123de820 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-async-block-mismatch.without_aob.stderr @@ -0,0 +1,32 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-async-block-mismatch.rs:65:1 + | +LL | fn trouble() -> impl Send { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-async-block-mismatch.rs:65:1 + | +LL | fn trouble() -> impl Send { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-async-block-mismatch.rs:65:1 + | +LL | fn trouble() -> impl Send { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-async-block-mismatch.rs:65:1 + | +LL | fn trouble() -> impl Send { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/async-await/dxf-coroutine-send-cross-crate.rs b/tests/ui/async-await/dxf-coroutine-send-cross-crate.rs new file mode 100644 index 0000000000000..d295c9a94e9b3 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-cross-crate.rs @@ -0,0 +1,12 @@ +//@ edition: 2024 +//@ aux-build: dxf-cross-crate-lib.rs +//@ compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf +//@ check-pass + +extern crate dxf_cross_crate_lib; + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(dxf_cross_crate_lib::use_guarded(&())); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.rs b/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.rs new file mode 100644 index 0000000000000..7ce0532ffbde7 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.rs @@ -0,0 +1,60 @@ +//@ revisions: without_aob aob_only with_aob +//@ edition: 2024 +//@ [without_aob] compile-flags: -Znext-solver -Zdxf +//@ [without_aob] known-bug: #149235 +//@ [aob_only] compile-flags: -Znext-solver -Zassumptions-on-binders +//@ [aob_only] check-pass +//@ [with_aob] compile-flags: -Znext-solver -Zdxf -Zassumptions-on-binders +//@ [with_aob] check-pass + +// Minimized from issue #149235. +// +// `Wrapper` has `unsafe impl Send for Wrapper` +// and `impl ObjectMarker for dyn Any` (implicitly `dyn Any + 'static`). After MIR +// region erasure, `'static` on the `dyn Any` becomes `ReErased`. +// `coroutine_hidden_types` rebinds it as a universally-quantified `BoundVar`. +// The solver must prove `Wrapper: Send` for placeholder `'!0`, +// which requires `dyn Any + '!0: ObjectMarker`. But `ObjectMarker` is only +// implemented for `dyn Any + 'static`, not for arbitrary `'!0`. +// +// AoB seems to resolve it by accident because it might be discharging an outlive +// obligation. + +#![allow(dead_code, private_bounds)] + +use std::any::Any; +use std::future::Future; +use std::marker::PhantomData; + +pub struct HasDropImpl; + +impl Drop for HasDropImpl { + fn drop(&mut self) {} +} + +pub struct Wrapper { + raw: HasDropImpl, + _param: PhantomData, +} + +unsafe impl Send for Wrapper {} + +trait ObjectMarker {} + +impl ObjectMarker for dyn Any {} + +fn fails() -> Result<(), Wrapper> { + Ok(()) +} + +async fn fut1() {} + +fn assert_send(_: F) {} + +fn main() { + let fut = async { + let _across_await = fails(); + fut1().await; + }; + assert_send(fut); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.without_aob.stderr b/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.without_aob.stderr new file mode 100644 index 0000000000000..9171db0e36414 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-dyn-object-static.without_aob.stderr @@ -0,0 +1,8 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-dyn-object-static.rs:59:5 + | +LL | assert_send(fut); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.rs b/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.rs new file mode 100644 index 0000000000000..60fadac9bf33d --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.rs @@ -0,0 +1,44 @@ +//@ revisions: without_aob with_aob +//@ edition: 2024 +//@ [without_aob] compile-flags: -Znext-solver -Zdxf +//@ [without_aob] known-bug: #126551 +//@ [with_aob] compile-flags: -Znext-solver -Zdxf -Zassumptions-on-binders +//@ [with_aob] check-pass + +// Minimized from futures `join_all` + `then`/`map` combinators. +// +// `async { &() }` produces a coroutine whose type contains `&'static ()`. After MIR +// region erasure, `'static` becomes `ReErased`, then `coroutine_hidden_types` rebinds it +// as a universally-quantified `BoundVar`. When the auto-trait solver checks `Send` for +// the outer coroutine's witness, it opens the binder and gets `&'!1_0 ()` — a placeholder. +// In erased mode, it can't prove the inner coroutine is `Send`. +// +// With `-Zassumptions-on-binders`, the solver can use the NLL-derived assumption to prove +// `(): '!1_0` because `'!1_0` outlives `'static`, and so is the coroutine `Send`. + +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +enum MaybeDone { + Future(F), + Done(F::Output), +} + +struct Map(Fut, F); + +impl T, T> Future for Map { + type Output = T; + fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { todo!() } +} + +async fn foo() { + let _md = MaybeDone::Future(Map(async { &() }, |_| async {})); + async {}.await; +} + +fn assert_send(_: T) {} + +fn main() { + assert_send(foo()); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.without_aob.stderr b/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.without_aob.stderr new file mode 100644 index 0000000000000..a68bf29004241 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-fnonce-closure.without_aob.stderr @@ -0,0 +1,16 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-fnonce-closure.rs:43:5 + | +LL | assert_send(foo()); + | ^^^^^^^^^^^^^^^^^^ + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-fnonce-closure.rs:43:5 + | +LL | assert_send(foo()); + | ^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/async-await/dxf-coroutine-send-negative.rs b/tests/ui/async-await/dxf-coroutine-send-negative.rs new file mode 100644 index 0000000000000..980c73137de10 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-negative.rs @@ -0,0 +1,40 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zdxf + +#![allow(dead_code)] + +use std::marker::PhantomData; + +struct NeedsMerge<'a, 'b> { + _raw: *const (), + _inv_a: PhantomData &'a ()>, + _inv_b: PhantomData &'b ()>, +} + +unsafe impl<'a> Send for NeedsMerge<'a, 'a> {} + +async fn yield_point() {} + +async fn use_phantom(data1: &(), data2: &()) { + let val = NeedsMerge { + _raw: std::ptr::null(), + _inv_a: PhantomData, // '?p + _inv_b: PhantomData, // '?q + }; + yield_point().await; + drop(val); +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(use_phantom(&(), &())); + //~^ ERROR + //~| ERROR + // The reason is NLL cannot deduce from the coroutine body that the two + // regions are actually the same. + // The two existential lifetimes '?p and '?q are independent to one another. + // They are left unconstrained by the implicit lifetime generics. + // The analysis is still sound, because validity of the coroutine body + // may depend on the fact that the two regions are independent. +} diff --git a/tests/ui/async-await/dxf-coroutine-send-negative.stderr b/tests/ui/async-await/dxf-coroutine-send-negative.stderr new file mode 100644 index 0000000000000..0b86c6364a5a9 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-negative.stderr @@ -0,0 +1,16 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-negative.rs:31:5 + | +LL | assert_send(use_phantom(&(), &())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-negative.rs:31:5 + | +LL | assert_send(use_phantom(&(), &())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/async-await/dxf-coroutine-send-nested.dxf.stderr b/tests/ui/async-await/dxf-coroutine-send-nested.dxf.stderr new file mode 100644 index 0000000000000..4450b08af2684 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-nested.dxf.stderr @@ -0,0 +1,16 @@ +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-nested.rs:37:5 + | +LL | assert_send(outer(&())); + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: higher-ranked subtype error + --> $DIR/dxf-coroutine-send-nested.rs:37:5 + | +LL | assert_send(outer(&())); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 2 previous errors + diff --git a/tests/ui/async-await/dxf-coroutine-send-nested.rs b/tests/ui/async-await/dxf-coroutine-send-nested.rs new file mode 100644 index 0000000000000..008e0546e9991 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-nested.rs @@ -0,0 +1,44 @@ +//@ edition: 2024 +//@ revisions: no_dxf dxf +//@[no_dxf] compile-flags: -Znext-solver -Zassumptions-on-binders +//@[no_dxf] check-pass +//@[dxf] compile-flags: -Znext-solver -Zdxf + +#![allow(dead_code)] + +use std::marker::PhantomData; + +struct NeedsMerge<'a, 'b> { + _raw: *const (), + _inv_a: PhantomData &'a ()>, + _inv_b: PhantomData &'b ()>, +} + +unsafe impl<'a> Send for NeedsMerge<'a, 'a> {} + +async fn inner(data: &()) { + let val = NeedsMerge { + _raw: std::ptr::null(), + _inv_a: PhantomData, // '?long + _inv_b: PhantomData, // '?short + }; + std::future::pending::<()>().await; + drop(val); +} + +async fn outer(data: &()) { + // The future returned by inner(data) is held across a yield. + inner(data).await; +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(outer(&())); + //[dxf]~^ ERROR + //[dxf]~| ERROR + // I think -Zassumptions-on-binders might be unsound here. + // I can see that '?long outlives '?short, which -Zdxf can compute by + // maximizing the outlive relations. + // It cannot prove the other direction of the outlive relation. +} diff --git a/tests/ui/async-await/dxf-coroutine-send-outlives.no_dxf.stderr b/tests/ui/async-await/dxf-coroutine-send-outlives.no_dxf.stderr new file mode 100644 index 0000000000000..99caca8d986ff --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-outlives.no_dxf.stderr @@ -0,0 +1,33 @@ +error[E0277]: `{coroutine witness@$DIR/dxf-coroutine-send-outlives.rs:23:33: 29:2}` cannot be sent between threads safely + --> $DIR/dxf-coroutine-send-outlives.rs:34:17 + | +LL | async fn use_guarded(data: &()) { + | - within this `impl Future` +... +LL | assert_send(use_guarded(&())); + | ----------- ^^^^^^^^^^^^^^^^ `{coroutine witness@$DIR/dxf-coroutine-send-outlives.rs:23:33: 29:2}` cannot be sent between threads safely + | | + | required by a bound introduced by this call + | + = help: within `impl Future`, the trait `Send` is not implemented for `{coroutine witness@$DIR/dxf-coroutine-send-outlives.rs:23:33: 29:2}` +note: required because it's used within this `async` fn body + --> $DIR/dxf-coroutine-send-outlives.rs:23:33 + | +LL | async fn use_guarded(data: &()) { + | _________________________________^ +LL | | let mut r1 = data; +LL | | let mut r2 = data; +LL | | let g = make_guarded(&mut r1, &mut r2); +LL | | std::future::pending::<()>().await; +LL | | drop(g); +LL | | } + | |_^ +note: required by a bound in `assert_send` + --> $DIR/dxf-coroutine-send-outlives.rs:31:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/async-await/dxf-coroutine-send-outlives.rs b/tests/ui/async-await/dxf-coroutine-send-outlives.rs new file mode 100644 index 0000000000000..3067718e1824c --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-outlives.rs @@ -0,0 +1,36 @@ +//@ edition: 2024 +//@ revisions: no_dxf dxf +//@[no_dxf] compile-flags: -Znext-solver -Zassumptions-on-binders +//@[dxf] compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf +//@[dxf] check-pass + +#![allow(dead_code)] + +use std::marker::PhantomData; + +struct Guarded<'a, 'b> { + _p: PhantomData<(fn(&'a ()) -> &'a (), fn(&'b ()) -> &'b ())>, + _raw: *mut (), +} + +// Send requires 'a: 'b outlives instead of equality. +unsafe impl<'a: 'b, 'b> Send for Guarded<'a, 'b> {} + +fn make_guarded<'a, 'b>(_x: &mut &'a (), _y: &mut &'b ()) -> Guarded<'a, 'b> { + Guarded { _p: PhantomData, _raw: std::ptr::null_mut() } +} + +async fn use_guarded(data: &()) { + let mut r1 = data; + let mut r2 = data; + let g = make_guarded(&mut r1, &mut r2); + std::future::pending::<()>().await; + drop(g); +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(use_guarded(&())); + //[no_dxf]~^ ERROR `{coroutine witness +} diff --git a/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.rs b/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.rs new file mode 100644 index 0000000000000..2c640abd19a63 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.rs @@ -0,0 +1,42 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf + +#![allow(dead_code)] + +// Under -Zdxf, the lifetime error should be stalled/ambiguous, but the Rc error +// should fail faster. +// We should only see the Rc error. + +use std::marker::PhantomData; +use std::rc::Rc; + +struct Guarded<'a, 'b> { + _p: PhantomData<(fn(&'a ()) -> &'a (), fn(&'b ()) -> &'b ())>, + _raw: *mut (), +} + +// Send requires 'b: 'a (outlives, not just equality). +unsafe impl<'a, 'b: 'a> Send for Guarded<'a, 'b> {} + +fn make_guarded<'a>(_x: &mut &'a (), _y: &mut &'a ()) -> Guarded<'a, 'a> { + Guarded { _p: PhantomData, _raw: std::ptr::null_mut() } +} + +async fn use_guarded_and_rc(data: &()) { + let mut r1 = data; + let mut r2 = data; + let g = make_guarded(&mut r1, &mut r2); + let rc = Rc::new(42); + std::future::pending::<()>().await; + drop(g); + drop(rc); +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(use_guarded_and_rc(&())); + //~^ ERROR future cannot be sent between threads safely + // We should NOT see an error about `Guarded` or coroutine witness Send here, + // as that part is stalled. +} diff --git a/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.stderr b/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.stderr new file mode 100644 index 0000000000000..f045a25610cb7 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-rc-and-lifetime.stderr @@ -0,0 +1,22 @@ +error: future cannot be sent between threads safely + --> $DIR/dxf-coroutine-send-rc-and-lifetime.rs:38:17 + | +LL | assert_send(use_guarded_and_rc(&())); + | ^^^^^^^^^^^^^^^^^^^^^^^ future returned by `use_guarded_and_rc` is not `Send` + | + = help: within `impl Future`, the trait `Send` is not implemented for `Rc` +note: future is not `Send` as this value is used across an await + --> $DIR/dxf-coroutine-send-rc-and-lifetime.rs:30:34 + | +LL | let rc = Rc::new(42); + | -- has type `Rc` which is not `Send` +LL | std::future::pending::<()>().await; + | ^^^^^ await occurs here, with `rc` maybe used later +note: required by a bound in `assert_send` + --> $DIR/dxf-coroutine-send-rc-and-lifetime.rs:35:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 1 previous error + diff --git a/tests/ui/async-await/dxf-coroutine-send-rpit-cycle.rs b/tests/ui/async-await/dxf-coroutine-send-rpit-cycle.rs new file mode 100644 index 0000000000000..12524a3680dda --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-rpit-cycle.rs @@ -0,0 +1,36 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zdxf +//@ check-pass + +use std::future::Future; +use std::task::{Context, Poll}; +use std::pin::{Pin, pin}; + +#[derive(Clone)] +struct Foo; + +pub enum MaybeDone { + Future(F), + Done(F::Output), + Gone, +} + +impl> Future for MaybeDone { + type Output = (); + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<()> { + Poll::Ready(()) + } +} + +async fn do_work(_: Foo) {} + +pub fn serve() -> impl Future + Send { + async move { + let netstack = Foo; + let work_fut = do_work(netstack.clone()); + let fut = pin!(MaybeDone::Future(work_fut)); + fut.await + } +} + +fn main() {} diff --git a/tests/ui/async-await/dxf-coroutine-send-strict-outlives.rs b/tests/ui/async-await/dxf-coroutine-send-strict-outlives.rs new file mode 100644 index 0000000000000..16d674721b235 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-strict-outlives.rs @@ -0,0 +1,34 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf + +#![allow(dead_code)] +use std::marker::PhantomData; + +struct Guarded<'a, 'b> { + _p: PhantomData<(fn(&'a ()) -> &'a (), fn(&'b ()) -> &'b ())>, +} +unsafe impl<'a, 'b: 'a> Send for Guarded<'a, 'b> {} + +async fn yield_point() {} + +async fn use_guarded<'a, 'b>(data1: &'a u8, data2: &'b u8) { + let mut r1 = data1; + let r2 = data2; + r1 = r2; // creates NLL constraint but does NOT prove 'b: 'a + let g = Guarded::<'a, 'b> { _p: PhantomData }; + yield_point().await; + drop(g); +} + +fn assert_send(_: impl Send) {} + +fn main() { + let data2 = 2u8; + { + let data1 = 1u8; + assert_send(use_guarded(&data1, &data2)); + //~^ ERROR cannot be sent between threads safely + // Even after maximizing the outlive graph, we cannot prove 'b: 'a + // to apply `impl Send for Guarded`, which is correct. + } +} diff --git a/tests/ui/async-await/dxf-coroutine-send-strict-outlives.stderr b/tests/ui/async-await/dxf-coroutine-send-strict-outlives.stderr new file mode 100644 index 0000000000000..bcc6be96cf92d --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-strict-outlives.stderr @@ -0,0 +1,44 @@ +error[E0277]: `{coroutine witness@$DIR/dxf-coroutine-send-strict-outlives.rs:14:60: 21:2}` cannot be sent between threads safely + --> $DIR/dxf-coroutine-send-strict-outlives.rs:29:9 + | +LL | async fn use_guarded<'a, 'b>(data1: &'a u8, data2: &'b u8) { + | - within this `impl Future` +... +LL | assert_send(use_guarded(&data1, &data2)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `{coroutine witness@$DIR/dxf-coroutine-send-strict-outlives.rs:14:60: 21:2}` cannot be sent between threads safely + | + = help: within `impl Future`, the trait `Send` is not implemented for `{coroutine witness@$DIR/dxf-coroutine-send-strict-outlives.rs:14:60: 21:2}` +note: required because it's used within this `async` fn body + --> $DIR/dxf-coroutine-send-strict-outlives.rs:14:60 + | +LL | async fn use_guarded<'a, 'b>(data1: &'a u8, data2: &'b u8) { + | ____________________________________________________________^ +LL | | let mut r1 = data1; +LL | | let r2 = data2; +LL | | r1 = r2; // creates NLL constraint but does NOT prove 'b: 'a +... | +LL | | drop(g); +LL | | } + | |_^ +note: required by a bound in `assert_send` + --> $DIR/dxf-coroutine-send-strict-outlives.rs:23:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +note: this `async` fn holds references with different lifetimes across an `.await` point; the `Send` trait requires these lifetimes to satisfy certain bounds, but the borrow checker could not verify them + --> $DIR/dxf-coroutine-send-strict-outlives.rs:14:60 + | +LL | async fn use_guarded<'a, 'b>(data1: &'a u8, data2: &'b u8) { + | ____________________________________________________________^ +LL | | let mut r1 = data1; +LL | | let r2 = data2; +LL | | r1 = r2; // creates NLL constraint but does NOT prove 'b: 'a +... | +LL | | drop(g); +LL | | } + | |_^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/async-await/dxf-coroutine-send-trivial.rs b/tests/ui/async-await/dxf-coroutine-send-trivial.rs new file mode 100644 index 0000000000000..bbbd9ddc8edd3 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-trivial.rs @@ -0,0 +1,26 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zdxf +//@ check-pass + +// This should not break anything. + +#![allow(dead_code)] + +async fn yield_point() {} + +async fn simple_send() { + let x: i32 = 42; + yield_point().await; + let _ = x; +} + +async fn with_ref(data: &i32) -> i32 { + yield_point().await; + *data +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(simple_send()); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-universe-ice.rs b/tests/ui/async-await/dxf-coroutine-send-universe-ice.rs new file mode 100644 index 0000000000000..8b748f70efa1a --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-universe-ice.rs @@ -0,0 +1,64 @@ +//@ edition: 2024 +//@ compile-flags: -Znext-solver -Zdxf +//@ check-pass + +// We should play nice with universes in static coroutines. + +#![feature(coroutines, stmt_expr_attributes)] +#![allow(dead_code)] + +fn assert_send(_: T) {} + +// === U1: Static coroutine with bound regions across yield === +fn case_u1_coroutine_with_ref() { + assert_send(#[coroutine] static |_: ()| { + let x = 42i32; + let r = &x; + yield; + let _ = *r; + }); +} + +// === U2: Nested static coroutine === +fn case_u2_nested_coroutine() { + assert_send(#[coroutine] static |_: ()| { + let x = 42i32; + let r = &x; + let inner = #[coroutine] static |_: ()| { + let y = 100i32; + let s = &y; + yield; + let _ = *s; + }; + yield; + let _ = (*r, inner); + }); +} + +// === U3: Triple-nested static coroutine === +fn case_u3_triple_nested() { + assert_send(#[coroutine] static |_: ()| { + let a = 1i32; + let ra = &a; + let mid = #[coroutine] static |_: ()| { + let b = 2i32; + let rb = &b; + let deep = #[coroutine] static |_: ()| { + let c = 3i32; + let rc = &c; + yield; + let _ = *rc; + }; + yield; + let _ = (*rb, deep); + }; + yield; + let _ = (*ra, mid); + }); +} + +fn main() { + case_u1_coroutine_with_ref(); + case_u2_nested_coroutine(); + case_u3_triple_nested(); +} diff --git a/tests/ui/async-await/dxf-coroutine-send-universe.rs b/tests/ui/async-await/dxf-coroutine-send-universe.rs new file mode 100644 index 0000000000000..519beb9b9ec23 --- /dev/null +++ b/tests/ui/async-await/dxf-coroutine-send-universe.rs @@ -0,0 +1,61 @@ +//@ edition: 2024 +//@ revisions: no_dxf dxf +//@[no_dxf] compile-flags: -Znext-solver +//@[no_dxf] check-pass +//@[dxf] compile-flags: -Znext-solver -Zdxf +//@[dxf] check-pass + +#![allow(unused)] + +use std::future::Future; + +fn assert_send(_: T) {} + +// === Case 1: dyn for<'a> Fn held across await creates U1 placeholder === +// The `for<'a>` in `Box Fn(&'a i32) + Send>` creates +// PlaceholderRegion(!1) when the solver decomposes the witness for Send. +async fn test_dyn_fn() { + let f: Box Fn(&'a i32) + Send> = Box::new(|_| {}); + async {}.await; + f(&1); +} + +// === Case 2: Conditional Send via for<'a> bound creates U1 placeholder === +trait Foo<'a> {} +impl<'a> Foo<'a> for () {} + +struct Bar(T); +unsafe impl Send for Bar where T: for<'a> Foo<'a> {} + +async fn test_hrtb() { + let x = Bar(()); + async {}.await; + drop(x); +} + +// === Case 3: GAT with for<'a> Send bound creates U1 placeholder === +trait GatTrait { + type Assoc<'a>; +} + +impl GatTrait for () { + type Assoc<'a> = &'a i32; +} + +struct GatBar(T); +unsafe impl Send for GatBar +where + for<'a> ::Assoc<'a>: Send, +{} + +async fn test_gat() { + let x = GatBar(()); + async {}.await; + drop(x); +} + +fn main() { + assert_send(test_dyn_fn()); + assert_send(test_hrtb()); + assert_send(test_gat()); +} diff --git a/tests/ui/async-await/scc-merge-outlives.no_dxf.stderr b/tests/ui/async-await/scc-merge-outlives.no_dxf.stderr new file mode 100644 index 0000000000000..0259dd3a19c90 --- /dev/null +++ b/tests/ui/async-await/scc-merge-outlives.no_dxf.stderr @@ -0,0 +1,32 @@ +error[E0277]: `{coroutine witness@$DIR/scc-merge-outlives.rs:23:33: 33:2}` cannot be sent between threads safely + --> $DIR/scc-merge-outlives.rs:38:17 + | +LL | async fn use_guarded(data: &()) { + | - within this `impl Future` +... +LL | assert_send(use_guarded(&())); + | ----------- ^^^^^^^^^^^^^^^^ `{coroutine witness@$DIR/scc-merge-outlives.rs:23:33: 33:2}` cannot be sent between threads safely + | | + | required by a bound introduced by this call + | + = help: within `impl Future`, the trait `Send` is not implemented for `{coroutine witness@$DIR/scc-merge-outlives.rs:23:33: 33:2}` +note: required because it's used within this `async` fn body + --> $DIR/scc-merge-outlives.rs:23:33 + | +LL | async fn use_guarded(data: &()) { + | _________________________________^ +LL | | let mut r1 = data; // '?r1 +LL | | let mut r2 = data; // '?r2 +... | +LL | | drop(g); +LL | | } + | |_^ +note: required by a bound in `assert_send` + --> $DIR/scc-merge-outlives.rs:35:24 + | +LL | fn assert_send(_: impl Send) {} + | ^^^^ required by this bound in `assert_send` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/async-await/scc-merge-outlives.rs b/tests/ui/async-await/scc-merge-outlives.rs new file mode 100644 index 0000000000000..c37a4cbbc71fc --- /dev/null +++ b/tests/ui/async-await/scc-merge-outlives.rs @@ -0,0 +1,40 @@ +//@ edition: 2024 +//@ revisions: no_dxf dxf +//@[no_dxf] compile-flags: -Znext-solver -Zassumptions-on-binders +//@[dxf] compile-flags: -Znext-solver -Zassumptions-on-binders -Zdxf +//@[dxf] check-pass + +#![allow(dead_code)] + +use std::marker::PhantomData; + +struct Guarded<'a, 'b> { + _p: PhantomData<(fn(&'a ()) -> &'a (), fn(&'b ()) -> &'b ())>, + _raw: *mut (), +} + +// Send only requires 'b: 'a, not equality. +unsafe impl<'a, 'b: 'a> Send for Guarded<'a, 'b> {} + +fn make_guarded<'a>(_x: &mut &'a (), _y: &mut &'a ()) -> Guarded<'a, 'a> { + Guarded { _p: PhantomData, _raw: std::ptr::null_mut() } +} + +async fn use_guarded(data: &()) { + let mut r1 = data; // '?r1 + let mut r2 = data; // '?r2 + // make_guarded takes &mut &'a (), so 'a is invariant. + // Both `r1` and `r2` reborrow from `data`, so '?r1 and '?r2 are in the same + // SCC. + // This is enough for the Send bound to apply. + let g = make_guarded(&mut r1, &mut r2); + std::future::pending::<()>().await; + drop(g); +} + +fn assert_send(_: impl Send) {} + +fn main() { + assert_send(use_guarded(&())); + //[no_dxf]~^ ERROR `{coroutine witness +}