diff --git a/compiler/rustc_middle/src/ty/util.rs b/compiler/rustc_middle/src/ty/util.rs index eb903ebfd99..ba05135638e 100644 --- a/compiler/rustc_middle/src/ty/util.rs +++ b/compiler/rustc_middle/src/ty/util.rs @@ -518,6 +518,42 @@ impl<'tcx> TyCtxt<'tcx> { Ok(()) } + /// Checks whether each generic argument is simply a unique generic placeholder. + /// + /// This is used in the new solver, which canonicalizes params to placeholders + /// for better caching. + pub fn uses_unique_placeholders_ignoring_regions( + self, + substs: SubstsRef<'tcx>, + ) -> Result<(), NotUniqueParam<'tcx>> { + let mut seen = GrowableBitSet::default(); + for arg in substs { + match arg.unpack() { + // Ignore regions, since we can't resolve those in a canonicalized + // query in the trait solver. + GenericArgKind::Lifetime(_) => {} + GenericArgKind::Type(t) => match t.kind() { + ty::Placeholder(p) => { + if !seen.insert(p.bound.var) { + return Err(NotUniqueParam::DuplicateParam(t.into())); + } + } + _ => return Err(NotUniqueParam::NotParam(t.into())), + }, + GenericArgKind::Const(c) => match c.kind() { + ty::ConstKind::Placeholder(p) => { + if !seen.insert(p.bound) { + return Err(NotUniqueParam::DuplicateParam(c.into())); + } + } + _ => return Err(NotUniqueParam::NotParam(c.into())), + }, + } + } + + Ok(()) + } + /// Returns `true` if `def_id` refers to a closure (e.g., `|x| x * 2`). Note /// that closures have a `DefId`, but the closure *expression* also /// has a `HirId` that is located within the context where the diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index bd83666eb1e..32a8bda2663 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -10,7 +10,8 @@ use rustc_infer::traits::query::NoSolution; use rustc_infer::traits::ObligationCause; use rustc_middle::infer::unify_key::{ConstVariableOrigin, ConstVariableOriginKind}; use rustc_middle::traits::solve::{ - CanonicalInput, Certainty, MaybeCause, PredefinedOpaques, PredefinedOpaquesData, QueryResult, + CanonicalInput, CanonicalResponse, Certainty, MaybeCause, PredefinedOpaques, + PredefinedOpaquesData, QueryResult, }; use rustc_middle::traits::DefiningAnchor; use rustc_middle::ty::{ @@ -726,7 +727,12 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { } } - pub(super) fn handle_opaque_ty( + pub(super) fn can_define_opaque_ty(&mut self, def_id: DefId) -> bool { + let Some(def_id) = def_id.as_local() else { return false; }; + self.infcx.opaque_type_origin(def_id).is_some() + } + + pub(super) fn register_opaque_ty( &mut self, a: Ty<'tcx>, b: Ty<'tcx>, @@ -737,4 +743,42 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { self.add_goals(obligations.into_iter().map(|obligation| obligation.into())); Ok(()) } + + // Do something for each opaque/hidden pair defined with `def_id` in the + // current inference context. + pub(super) fn unify_existing_opaque_tys( + &mut self, + param_env: ty::ParamEnv<'tcx>, + key: ty::AliasTy<'tcx>, + ty: Ty<'tcx>, + ) -> Vec> { + let Some(def_id) = key.def_id.as_local() else { return vec![]; }; + + // FIXME: Super inefficient to be cloning this... + let opaques = self.infcx.clone_opaque_types_for_query_response(); + + let mut values = vec![]; + for (candidate_key, candidate_ty) in opaques { + if candidate_key.def_id != def_id { + continue; + } + values.extend(self.probe(|ecx| { + for (a, b) in std::iter::zip(candidate_key.substs, key.substs) { + ecx.eq(param_env, a, b)?; + } + ecx.eq(param_env, candidate_ty, ty)?; + let mut obl = vec![]; + ecx.infcx.add_item_bounds_for_hidden_type( + candidate_key, + ObligationCause::dummy(), + param_env, + candidate_ty, + &mut obl, + ); + ecx.add_goals(obl.into_iter().map(Into::into)); + ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + })); + } + values + } } diff --git a/compiler/rustc_trait_selection/src/solve/opaques.rs b/compiler/rustc_trait_selection/src/solve/opaques.rs index 7d6e1647cfc..1d5bb46450c 100644 --- a/compiler/rustc_trait_selection/src/solve/opaques.rs +++ b/compiler/rustc_trait_selection/src/solve/opaques.rs @@ -1,6 +1,8 @@ +use rustc_middle::traits::query::NoSolution; use rustc_middle::traits::solve::{Certainty, Goal, QueryResult}; -use rustc_middle::traits::Reveal; -use rustc_middle::ty::{self}; +use rustc_middle::traits::{ObligationCause, Reveal}; +use rustc_middle::ty; +use rustc_middle::ty::util::NotUniqueParam; use super::{EvalCtxt, SolverMode}; @@ -15,22 +17,53 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { match goal.param_env.reveal() { Reveal::UserFacing => match self.solver_mode() { - SolverMode::Normal => self.probe(|ecx| { - // FIXME: Check that the usage is "defining" (all free params), otherwise bail. - // FIXME: This should probably just check the anchor directly + SolverMode::Normal => { + // FIXME: at some point we should call queries without defining + // new opaque types but having the existing opaque type definitions. + // This will require moving this below "Prefer opaques registered already". + if !self.can_define_opaque_ty(opaque_ty.def_id) { + return Err(NoSolution); + } + // FIXME: This may have issues when the substs contain aliases... + match self.tcx().uses_unique_placeholders_ignoring_regions(opaque_ty.substs) { + Err(NotUniqueParam::NotParam(param)) if param.is_non_region_infer() => { + return self.evaluate_added_goals_and_make_canonical_response( + Certainty::AMBIGUOUS, + ); + } + Err(_) => { + return Err(NoSolution); + } + Ok(()) => {} + } + // Prefer opaques registered already. + let matches = self.unify_existing_opaque_tys( + goal.param_env, + opaque_ty, + expected + ); + if !matches.is_empty() { + if let Some(response) = self.try_merge_responses(&matches) { + return Ok(response); + } else { + return self.flounder(&matches); + } + } + // Otherwise, define a new opaque type let opaque_ty = tcx.mk_opaque(opaque_ty.def_id, opaque_ty.substs); - ecx.handle_opaque_ty(expected, opaque_ty, goal.param_env)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }), + self.register_opaque_ty(expected, opaque_ty, goal.param_env)?; + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } SolverMode::Coherence => { self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS) } }, - Reveal::All => self.probe(|ecx| { + Reveal::All => { + // FIXME: Add an assertion that opaque type storage is empty. let actual = tcx.type_of(opaque_ty.def_id).subst(tcx, opaque_ty.substs); - ecx.eq(goal.param_env, expected, actual)?; - ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) - }), + self.eq(goal.param_env, expected, actual)?; + self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes) + } } } }