blob: faf218131b8b142c416d7b629a9e91290837243c [file] [log] [blame]
use crate::infer::canonical::{
Canonical, CanonicalQueryResponse, OriginalQueryValues, QueryRegionConstraints,
};
use crate::infer::{InferCtxt, InferOk};
use crate::traits::{ObligationCause, ObligationCtxt};
use rustc_errors::ErrorGuaranteed;
use rustc_infer::infer::canonical::Certainty;
use rustc_infer::traits::PredicateObligations;
use rustc_middle::traits::query::NoSolution;
use rustc_middle::ty::fold::TypeFoldable;
use rustc_middle::ty::{ParamEnvAnd, TyCtxt};
use rustc_span::Span;
use std::fmt;
pub mod ascribe_user_type;
pub mod custom;
pub mod eq;
pub mod implied_outlives_bounds;
pub mod normalize;
pub mod outlives;
pub mod prove_predicate;
pub mod subtype;
pub use rustc_middle::traits::query::type_op::*;
use self::custom::scrape_region_constraints;
/// "Type ops" are used in NLL to perform some particular action and
/// extract out the resulting region constraints (or an error if it
/// cannot be completed).
pub trait TypeOp<'tcx>: Sized + fmt::Debug {
type Output: fmt::Debug;
type ErrorInfo;
/// Processes the operation and all resulting obligations,
/// returning the final result along with any region constraints
/// (they will be given over to the NLL region solver).
fn fully_perform(
self,
infcx: &InferCtxt<'tcx>,
span: Span,
) -> Result<TypeOpOutput<'tcx, Self>, ErrorGuaranteed>;
}
/// The output from performing a type op
pub struct TypeOpOutput<'tcx, Op: TypeOp<'tcx>> {
/// The output from the type op.
pub output: Op::Output,
/// Any region constraints from performing the type op.
pub constraints: Option<&'tcx QueryRegionConstraints<'tcx>>,
/// Used for error reporting to be able to rerun the query
pub error_info: Option<Op::ErrorInfo>,
}
/// "Query type ops" are type ops that are implemented using a
/// [canonical query][c]. The `Self` type here contains the kernel of
/// information needed to do the operation -- `TypeOp` is actually
/// implemented for `ParamEnvAnd<Self>`, since we always need to bring
/// along a parameter environment as well. For query type-ops, we will
/// first canonicalize the key and then invoke the query on the tcx,
/// which produces the resulting query region constraints.
///
/// [c]: https://rust-lang.github.io/chalk/book/canonical_queries/canonicalization.html
pub trait QueryTypeOp<'tcx>: fmt::Debug + Copy + TypeFoldable<TyCtxt<'tcx>> + 'tcx {
type QueryResponse: TypeFoldable<TyCtxt<'tcx>>;
/// Give query the option for a simple fast path that never
/// actually hits the tcx cache lookup etc. Return `Some(r)` with
/// a final result or `None` to do the full path.
fn try_fast_path(
tcx: TyCtxt<'tcx>,
key: &ParamEnvAnd<'tcx, Self>,
) -> Option<Self::QueryResponse>;
/// Performs the actual query with the canonicalized key -- the
/// real work happens here. This method is not given an `infcx`
/// because it shouldn't need one -- and if it had access to one,
/// it might do things like invoke `sub_regions`, which would be
/// bad, because it would create subregion relationships that are
/// not captured in the return value.
fn perform_query(
tcx: TyCtxt<'tcx>,
canonicalized: Canonical<'tcx, ParamEnvAnd<'tcx, Self>>,
) -> Result<CanonicalQueryResponse<'tcx, Self::QueryResponse>, NoSolution>;
/// In the new trait solver, we already do caching in the solver itself,
/// so there's no need to canonicalize and cache via the query system.
/// Additionally, even if we were to canonicalize, we'd still need to
/// make sure to feed it predefined opaque types and the defining anchor
/// and that would require duplicating all of the tcx queries. Instead,
/// just perform these ops locally.
fn perform_locally_with_next_solver(
ocx: &ObligationCtxt<'_, 'tcx>,
key: ParamEnvAnd<'tcx, Self>,
) -> Result<Self::QueryResponse, NoSolution>;
fn fully_perform_into(
query_key: ParamEnvAnd<'tcx, Self>,
infcx: &InferCtxt<'tcx>,
output_query_region_constraints: &mut QueryRegionConstraints<'tcx>,
span: Span,
) -> Result<
(
Self::QueryResponse,
Option<Canonical<'tcx, ParamEnvAnd<'tcx, Self>>>,
PredicateObligations<'tcx>,
Certainty,
),
NoSolution,
> {
if let Some(result) = QueryTypeOp::try_fast_path(infcx.tcx, &query_key) {
return Ok((result, None, vec![], Certainty::Proven));
}
let mut canonical_var_values = OriginalQueryValues::default();
let old_param_env = query_key.param_env;
let canonical_self = infcx.canonicalize_query(query_key, &mut canonical_var_values);
let canonical_result = Self::perform_query(infcx.tcx, canonical_self)?;
let InferOk { value, obligations } = infcx
.instantiate_nll_query_response_and_region_obligations(
&ObligationCause::dummy_with_span(span),
old_param_env,
&canonical_var_values,
canonical_result,
output_query_region_constraints,
)?;
Ok((value, Some(canonical_self), obligations, canonical_result.value.certainty))
}
}
impl<'tcx, Q> TypeOp<'tcx> for ParamEnvAnd<'tcx, Q>
where
Q: QueryTypeOp<'tcx>,
{
type Output = Q::QueryResponse;
type ErrorInfo = Canonical<'tcx, ParamEnvAnd<'tcx, Q>>;
fn fully_perform(
self,
infcx: &InferCtxt<'tcx>,
span: Span,
) -> Result<TypeOpOutput<'tcx, Self>, ErrorGuaranteed> {
// In the new trait solver, query type ops are performed locally. This
// is because query type ops currently use the old canonicalizer, and
// that doesn't preserve things like opaques which have been registered
// during MIR typeck. Even after the old canonicalizer is gone, it's
// probably worthwhile just keeping this run-locally logic, since we
// probably don't gain much from caching here given the new solver does
// caching internally.
if infcx.next_trait_solver() {
return Ok(scrape_region_constraints(
infcx,
|ocx| QueryTypeOp::perform_locally_with_next_solver(ocx, self),
"query type op",
span,
)?
.0);
}
let mut region_constraints = QueryRegionConstraints::default();
let (output, error_info, mut obligations, _) =
Q::fully_perform_into(self, infcx, &mut region_constraints, span).map_err(|_| {
infcx.dcx().span_delayed_bug(span, format!("error performing {self:?}"))
})?;
// Typically, instantiating NLL query results does not
// create obligations. However, in some cases there
// are unresolved type variables, and unify them *can*
// create obligations. In that case, we have to go
// fulfill them. We do this via a (recursive) query.
while !obligations.is_empty() {
trace!("{:#?}", obligations);
let mut progress = false;
for obligation in std::mem::take(&mut obligations) {
let obligation = infcx.resolve_vars_if_possible(obligation);
match ProvePredicate::fully_perform_into(
obligation.param_env.and(ProvePredicate::new(obligation.predicate)),
infcx,
&mut region_constraints,
span,
) {
Ok(((), _, new, certainty)) => {
obligations.extend(new);
progress = true;
if let Certainty::Ambiguous = certainty {
obligations.push(obligation);
}
}
Err(_) => obligations.push(obligation),
}
}
if !progress {
infcx
.dcx()
.span_bug(span, format!("ambiguity processing {obligations:?} from {self:?}"));
}
}
Ok(TypeOpOutput {
output,
constraints: if region_constraints.is_empty() {
None
} else {
Some(infcx.tcx.arena.alloc(region_constraints))
},
error_info,
})
}
}