Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/traits.rs')
| -rw-r--r-- | crates/hir-ty/src/traits.rs | 278 |
1 files changed, 121 insertions, 157 deletions
diff --git a/crates/hir-ty/src/traits.rs b/crates/hir-ty/src/traits.rs index 7414b4fc60..7f6d4ff17f 100644 --- a/crates/hir-ty/src/traits.rs +++ b/crates/hir-ty/src/traits.rs @@ -1,72 +1,59 @@ -//! Trait solving using Chalk. +//! Trait solving using next trait solver. use core::fmt; -use std::env::var; - -use chalk_ir::{DebruijnIndex, GoalData, fold::TypeFoldable}; -use chalk_recursive::Cache; -use chalk_solve::{Solver, logging_db::LoggingRustIrDatabase, rust_ir}; +use std::hash::Hash; use base_db::Crate; use hir_def::{BlockId, TraitId, lang_item::LangItem}; use hir_expand::name::Name; use intern::sym; -use span::Edition; -use stdx::{never, panic_context}; +use rustc_next_trait_solver::solve::{HasChanged, SolverDelegateEvalExt}; +use rustc_type_ir::{ + TypingMode, + inherent::{IntoKind, Span as _}, + solve::Certainty, +}; use triomphe::Arc; use crate::{ - AliasEq, AliasTy, Canonical, DomainGoal, Goal, Guidance, InEnvironment, Interner, ProjectionTy, - ProjectionTyExt, Solution, TraitRefExt, Ty, TyKind, TypeFlags, WhereClause, db::HirDatabase, - infer::unify::InferenceTable, utils::UnevaluatedConstEvaluatorFolder, + db::HirDatabase, + next_solver::{ + Canonical, DbInterner, GenericArgs, Goal, ParamEnv, Predicate, SolverContext, Span, Ty, + TyKind, + infer::{DbInternerInferExt, InferCtxt, traits::ObligationCause}, + obligation_ctxt::ObligationCtxt, + }, }; -/// This controls how much 'time' we give the Chalk solver before giving up. -const CHALK_SOLVER_FUEL: i32 = 1000; - -#[derive(Debug, Copy, Clone)] -pub(crate) struct ChalkContext<'a> { - pub(crate) db: &'a dyn HirDatabase, - pub(crate) krate: Crate, - pub(crate) block: Option<BlockId>, -} - -fn create_chalk_solver() -> chalk_recursive::RecursiveSolver<Interner> { - let overflow_depth = - var("CHALK_OVERFLOW_DEPTH").ok().and_then(|s| s.parse().ok()).unwrap_or(500); - let max_size = var("CHALK_SOLVER_MAX_SIZE").ok().and_then(|s| s.parse().ok()).unwrap_or(150); - chalk_recursive::RecursiveSolver::new(overflow_depth, max_size, Some(Cache::new())) -} - /// A set of clauses that we assume to be true. E.g. if we are inside this function: /// ```rust /// fn foo<T: Default>(t: T) {} /// ``` /// we assume that `T: Default`. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct TraitEnvironment { +pub struct TraitEnvironment<'db> { pub krate: Crate, pub block: Option<BlockId>, // FIXME make this a BTreeMap - traits_from_clauses: Box<[(Ty, TraitId)]>, - pub env: chalk_ir::Environment<Interner>, + traits_from_clauses: Box<[(Ty<'db>, TraitId)]>, + pub env: ParamEnv<'db>, } -impl TraitEnvironment { +impl<'db> TraitEnvironment<'db> { pub fn empty(krate: Crate) -> Arc<Self> { Arc::new(TraitEnvironment { krate, block: None, traits_from_clauses: Box::default(), - env: chalk_ir::Environment::new(Interner), + env: ParamEnv::empty(), }) } pub fn new( krate: Crate, block: Option<BlockId>, - traits_from_clauses: Box<[(Ty, TraitId)]>, - env: chalk_ir::Environment<Interner>, + traits_from_clauses: Box<[(Ty<'db>, TraitId)]>, + env: ParamEnv<'db>, ) -> Arc<Self> { Arc::new(TraitEnvironment { krate, block, traits_from_clauses, env }) } @@ -76,135 +63,86 @@ impl TraitEnvironment { Arc::make_mut(this).block = Some(block); } - pub fn traits_in_scope_from_clauses(&self, ty: Ty) -> impl Iterator<Item = TraitId> + '_ { + pub fn traits_in_scope_from_clauses(&self, ty: Ty<'db>) -> impl Iterator<Item = TraitId> + '_ { self.traits_from_clauses .iter() .filter_map(move |(self_ty, trait_id)| (*self_ty == ty).then_some(*trait_id)) } } -pub(crate) fn normalize_projection_query( - db: &dyn HirDatabase, - projection: ProjectionTy, - env: Arc<TraitEnvironment>, -) -> Ty { - if projection.substitution.iter(Interner).any(|arg| { - arg.ty(Interner) - .is_some_and(|ty| ty.data(Interner).flags.intersects(TypeFlags::HAS_TY_INFER)) - }) { - never!( - "Invoking `normalize_projection_query` with a projection type containing inference var" - ); - return TyKind::Error.intern(Interner); - } +/// This should be used in `hir` only. +pub fn structurally_normalize_ty<'db>( + infcx: &InferCtxt<'db>, + ty: Ty<'db>, + env: Arc<TraitEnvironment<'db>>, +) -> Ty<'db> { + let TyKind::Alias(..) = ty.kind() else { return ty }; + let mut ocx = ObligationCtxt::new(infcx); + let ty = ocx.structurally_normalize_ty(&ObligationCause::dummy(), env.env, ty).unwrap_or(ty); + ty.replace_infer_with_error(infcx.interner) +} - let mut table = InferenceTable::new(db, env); - let ty = table.normalize_projection_ty(projection); - table.resolve_completely(ty) +#[derive(Clone, Debug, PartialEq)] +pub enum NextTraitSolveResult { + Certain, + Uncertain, + NoSolution, } -/// Solve a trait goal using Chalk. -pub(crate) fn trait_solve_query( - db: &dyn HirDatabase, - krate: Crate, - block: Option<BlockId>, - goal: Canonical<InEnvironment<Goal>>, -) -> Option<Solution> { - let _p = tracing::info_span!("trait_solve_query", detail = ?match &goal.value.goal.data(Interner) { - GoalData::DomainGoal(DomainGoal::Holds(WhereClause::Implemented(it))) => db - .trait_signature(it.hir_trait_id()) - .name - .display(db, Edition::LATEST) - .to_string(), - GoalData::DomainGoal(DomainGoal::Holds(WhereClause::AliasEq(_))) => "alias_eq".to_owned(), - _ => "??".to_owned(), - }) - .entered(); - - if let GoalData::DomainGoal(DomainGoal::Holds(WhereClause::AliasEq(AliasEq { - alias: AliasTy::Projection(projection_ty), - .. - }))) = &goal.value.goal.data(Interner) - { - if let TyKind::BoundVar(_) = projection_ty.self_type_parameter(db).kind(Interner) { - // Hack: don't ask Chalk to normalize with an unknown self type, it'll say that's impossible - return Some(Solution::Ambig(Guidance::Unknown)); - } +impl NextTraitSolveResult { + pub fn no_solution(&self) -> bool { + matches!(self, NextTraitSolveResult::NoSolution) } - // Chalk see `UnevaluatedConst` as a unique concrete value, but we see it as an alias for another const. So - // we should get rid of it when talking to chalk. - let goal = goal - .try_fold_with(&mut UnevaluatedConstEvaluatorFolder { db }, DebruijnIndex::INNERMOST) - .unwrap(); + pub fn certain(&self) -> bool { + matches!(self, NextTraitSolveResult::Certain) + } - // We currently don't deal with universes (I think / hope they're not yet - // relevant for our use cases?) - let u_canonical = chalk_ir::UCanonical { canonical: goal, universes: 1 }; - solve(db, krate, block, &u_canonical) + pub fn uncertain(&self) -> bool { + matches!(self, NextTraitSolveResult::Uncertain) + } } -fn solve( - db: &dyn HirDatabase, - krate: Crate, - block: Option<BlockId>, - goal: &chalk_ir::UCanonical<chalk_ir::InEnvironment<chalk_ir::Goal<Interner>>>, -) -> Option<chalk_solve::Solution<Interner>> { - let _p = tracing::info_span!("solve", ?krate, ?block).entered(); - let context = ChalkContext { db, krate, block }; - tracing::debug!("solve goal: {:?}", goal); - let mut solver = create_chalk_solver(); - - let fuel = std::cell::Cell::new(CHALK_SOLVER_FUEL); - - let should_continue = || { - db.unwind_if_revision_cancelled(); - let remaining = fuel.get(); - fuel.set(remaining - 1); - if remaining == 0 { - tracing::debug!("fuel exhausted"); - } - remaining > 0 - }; - - let mut solve = || { - let _ctx = if is_chalk_debug() || is_chalk_print() { - Some(panic_context::enter(format!("solving {goal:?}"))) - } else { - None - }; - let solution = if is_chalk_print() { - let logging_db = - LoggingRustIrDatabaseLoggingOnDrop(LoggingRustIrDatabase::new(context)); - solver.solve_limited(&logging_db.0, goal, &should_continue) - } else { - solver.solve_limited(&context, goal, &should_continue) - }; - - tracing::debug!("solve({:?}) => {:?}", goal, solution); - - solution - }; - - // don't set the TLS for Chalk unless Chalk debugging is active, to make - // extra sure we only use it for debugging - if is_chalk_debug() { crate::tls::set_current_program(db, solve) } else { solve() } -} +pub fn next_trait_solve_canonical_in_ctxt<'db>( + infer_ctxt: &InferCtxt<'db>, + goal: Canonical<'db, Goal<'db, Predicate<'db>>>, +) -> NextTraitSolveResult { + let context = SolverContext(infer_ctxt.clone()); + + tracing::info!(?goal); + + let (goal, var_values) = context.instantiate_canonical(&goal); + tracing::info!(?var_values); + + let res = context.evaluate_root_goal(goal, Span::dummy(), None); -struct LoggingRustIrDatabaseLoggingOnDrop<'a>(LoggingRustIrDatabase<Interner, ChalkContext<'a>>); + let res = res.map(|r| (r.has_changed, r.certainty)); -impl Drop for LoggingRustIrDatabaseLoggingOnDrop<'_> { - fn drop(&mut self) { - tracing::info!("chalk program:\n{}", self.0); + tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); + + match res { + Err(_) => NextTraitSolveResult::NoSolution, + Ok((_, Certainty::Yes)) => NextTraitSolveResult::Certain, + Ok((_, Certainty::Maybe { .. })) => NextTraitSolveResult::Uncertain, } } -fn is_chalk_debug() -> bool { - std::env::var("CHALK_DEBUG").is_ok() -} +/// Solve a trait goal using next trait solver. +pub fn next_trait_solve_in_ctxt<'db, 'a>( + infer_ctxt: &'a InferCtxt<'db>, + goal: Goal<'db, Predicate<'db>>, +) -> Result<(HasChanged, Certainty), rustc_type_ir::solve::NoSolution> { + tracing::info!(?goal); + + let context = <&SolverContext<'db>>::from(infer_ctxt); + + let res = context.evaluate_root_goal(goal, Span::dummy(), None); + + let res = res.map(|r| (r.has_changed, r.certainty)); -fn is_chalk_print() -> bool { - std::env::var("CHALK_PRINT").is_ok() + tracing::debug!("solve_nextsolver({:?}) => {:?}", goal, res); + + res } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -268,15 +206,6 @@ impl FnTrait { } } - pub const fn to_chalk_ir(self) -> rust_ir::ClosureKind { - // Chalk doesn't support async fn traits. - match self { - FnTrait::AsyncFnOnce | FnTrait::FnOnce => rust_ir::ClosureKind::FnOnce, - FnTrait::AsyncFnMut | FnTrait::FnMut => rust_ir::ClosureKind::FnMut, - FnTrait::AsyncFn | FnTrait::Fn => rust_ir::ClosureKind::Fn, - } - } - pub fn method_name(self) -> Name { match self { FnTrait::FnOnce => Name::new_symbol_root(sym::call_once), @@ -291,9 +220,44 @@ impl FnTrait { pub fn get_id(self, db: &dyn HirDatabase, krate: Crate) -> Option<TraitId> { self.lang_item().resolve_trait(db, krate) } +} - #[inline] - pub(crate) fn is_async(self) -> bool { - matches!(self, FnTrait::AsyncFn | FnTrait::AsyncFnMut | FnTrait::AsyncFnOnce) - } +/// This should not be used in `hir-ty`, only in `hir`. +pub fn implements_trait_unique<'db>( + ty: Ty<'db>, + db: &'db dyn HirDatabase, + env: Arc<TraitEnvironment<'db>>, + trait_: TraitId, +) -> bool { + implements_trait_unique_impl(db, env, trait_, &mut |infcx| { + infcx.fill_rest_fresh_args(trait_.into(), [ty.into()]) + }) +} + +/// This should not be used in `hir-ty`, only in `hir`. +pub fn implements_trait_unique_with_args<'db>( + db: &'db dyn HirDatabase, + env: Arc<TraitEnvironment<'db>>, + trait_: TraitId, + args: GenericArgs<'db>, +) -> bool { + implements_trait_unique_impl(db, env, trait_, &mut |_| args) +} + +fn implements_trait_unique_impl<'db>( + db: &'db dyn HirDatabase, + env: Arc<TraitEnvironment<'db>>, + trait_: TraitId, + create_args: &mut dyn FnMut(&InferCtxt<'db>) -> GenericArgs<'db>, +) -> bool { + let interner = DbInterner::new_with(db, Some(env.krate), env.block); + // FIXME(next-solver): I believe this should be `PostAnalysis`. + let infcx = interner.infer_ctxt().build(TypingMode::non_body_analysis()); + + let args = create_args(&infcx); + let trait_ref = rustc_type_ir::TraitRef::new_from_args(interner, trait_.into(), args); + let goal = Goal::new(interner, env.env, trait_ref); + + let result = crate::traits::next_trait_solve_in_ctxt(&infcx, goal); + matches!(result, Ok((_, Certainty::Yes))) } |