Check that opaque is a defining use, prefer pre-defined opaques

This commit is contained in:
Michael Goulet 2023-05-17 20:45:14 +00:00
parent f3c9c21658
commit e3f8beaed6
3 changed files with 127 additions and 14 deletions

View File

@ -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

View File

@ -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<CanonicalResponse<'tcx>> {
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
}
}

View File

@ -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)
}
}
}
}