aboutsummaryrefslogtreecommitdiff
path: root/crates/hir_ty/src/traits.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/hir_ty/src/traits.rs')
-rw-r--r--crates/hir_ty/src/traits.rs272
1 files changed, 272 insertions, 0 deletions
diff --git a/crates/hir_ty/src/traits.rs b/crates/hir_ty/src/traits.rs
new file mode 100644
index 000000000..255323717
--- /dev/null
+++ b/crates/hir_ty/src/traits.rs
@@ -0,0 +1,272 @@
1//! Trait solving using Chalk.
2use std::sync::Arc;
3
4use base_db::CrateId;
5use chalk_ir::cast::Cast;
6use chalk_solve::Solver;
7use hir_def::{lang_item::LangItemTarget, TraitId};
8
9use crate::{db::HirDatabase, DebruijnIndex, Substs};
10
11use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk};
12
13use self::chalk::{from_chalk, Interner, ToChalk};
14
15pub(crate) mod chalk;
16
17// This controls the maximum size of types Chalk considers. If we set this too
18// high, we can run into slow edge cases; if we set it too low, Chalk won't
19// find some solutions.
20// FIXME this is currently hardcoded in the recursive solver
21// const CHALK_SOLVER_MAX_SIZE: usize = 10;
22
23/// This controls how much 'time' we give the Chalk solver before giving up.
24const CHALK_SOLVER_FUEL: i32 = 100;
25
26#[derive(Debug, Copy, Clone)]
27struct ChalkContext<'a> {
28 db: &'a dyn HirDatabase,
29 krate: CrateId,
30}
31
32fn create_chalk_solver() -> chalk_recursive::RecursiveSolver<Interner> {
33 let overflow_depth = 100;
34 let caching_enabled = true;
35 chalk_recursive::RecursiveSolver::new(overflow_depth, caching_enabled)
36}
37
38/// A set of clauses that we assume to be true. E.g. if we are inside this function:
39/// ```rust
40/// fn foo<T: Default>(t: T) {}
41/// ```
42/// we assume that `T: Default`.
43#[derive(Clone, Debug, PartialEq, Eq, Hash)]
44pub struct TraitEnvironment {
45 pub predicates: Vec<GenericPredicate>,
46}
47
48impl TraitEnvironment {
49 /// Returns trait refs with the given self type which are supposed to hold
50 /// in this trait env. E.g. if we are in `foo<T: SomeTrait>()`, this will
51 /// find that `T: SomeTrait` if we call it for `T`.
52 pub(crate) fn trait_predicates_for_self_ty<'a>(
53 &'a self,
54 ty: &'a Ty,
55 ) -> impl Iterator<Item = &'a TraitRef> + 'a {
56 self.predicates.iter().filter_map(move |pred| match pred {
57 GenericPredicate::Implemented(tr) if tr.self_ty() == ty => Some(tr),
58 _ => None,
59 })
60 }
61}
62
63/// Something (usually a goal), along with an environment.
64#[derive(Clone, Debug, PartialEq, Eq, Hash)]
65pub struct InEnvironment<T> {
66 pub environment: Arc<TraitEnvironment>,
67 pub value: T,
68}
69
70impl<T> InEnvironment<T> {
71 pub fn new(environment: Arc<TraitEnvironment>, value: T) -> InEnvironment<T> {
72 InEnvironment { environment, value }
73 }
74}
75
76/// Something that needs to be proven (by Chalk) during type checking, e.g. that
77/// a certain type implements a certain trait. Proving the Obligation might
78/// result in additional information about inference variables.
79#[derive(Clone, Debug, PartialEq, Eq, Hash)]
80pub enum Obligation {
81 /// Prove that a certain type implements a trait (the type is the `Self` type
82 /// parameter to the `TraitRef`).
83 Trait(TraitRef),
84 Projection(ProjectionPredicate),
85}
86
87impl Obligation {
88 pub fn from_predicate(predicate: GenericPredicate) -> Option<Obligation> {
89 match predicate {
90 GenericPredicate::Implemented(trait_ref) => Some(Obligation::Trait(trait_ref)),
91 GenericPredicate::Projection(projection_pred) => {
92 Some(Obligation::Projection(projection_pred))
93 }
94 GenericPredicate::Error => None,
95 }
96 }
97}
98
99#[derive(Clone, Debug, PartialEq, Eq, Hash)]
100pub struct ProjectionPredicate {
101 pub projection_ty: ProjectionTy,
102 pub ty: Ty,
103}
104
105impl TypeWalk for ProjectionPredicate {
106 fn walk(&self, f: &mut impl FnMut(&Ty)) {
107 self.projection_ty.walk(f);
108 self.ty.walk(f);
109 }
110
111 fn walk_mut_binders(
112 &mut self,
113 f: &mut impl FnMut(&mut Ty, DebruijnIndex),
114 binders: DebruijnIndex,
115 ) {
116 self.projection_ty.walk_mut_binders(f, binders);
117 self.ty.walk_mut_binders(f, binders);
118 }
119}
120
121/// Solve a trait goal using Chalk.
122pub(crate) fn trait_solve_query(
123 db: &dyn HirDatabase,
124 krate: CrateId,
125 goal: Canonical<InEnvironment<Obligation>>,
126) -> Option<Solution> {
127 let _p = profile::span("trait_solve_query").detail(|| match &goal.value.value {
128 Obligation::Trait(it) => db.trait_data(it.trait_).name.to_string(),
129 Obligation::Projection(_) => "projection".to_string(),
130 });
131 log::info!("trait_solve_query({})", goal.value.value.display(db));
132
133 if let Obligation::Projection(pred) = &goal.value.value {
134 if let Ty::Bound(_) = &pred.projection_ty.parameters[0] {
135 // Hack: don't ask Chalk to normalize with an unknown self type, it'll say that's impossible
136 return Some(Solution::Ambig(Guidance::Unknown));
137 }
138 }
139
140 let canonical = goal.to_chalk(db).cast(&Interner);
141
142 // We currently don't deal with universes (I think / hope they're not yet
143 // relevant for our use cases?)
144 let u_canonical = chalk_ir::UCanonical { canonical, universes: 1 };
145 let solution = solve(db, krate, &u_canonical);
146 solution.map(|solution| solution_from_chalk(db, solution))
147}
148
149fn solve(
150 db: &dyn HirDatabase,
151 krate: CrateId,
152 goal: &chalk_ir::UCanonical<chalk_ir::InEnvironment<chalk_ir::Goal<Interner>>>,
153) -> Option<chalk_solve::Solution<Interner>> {
154 let context = ChalkContext { db, krate };
155 log::debug!("solve goal: {:?}", goal);
156 let mut solver = create_chalk_solver();
157
158 let fuel = std::cell::Cell::new(CHALK_SOLVER_FUEL);
159
160 let should_continue = || {
161 context.db.check_canceled();
162 let remaining = fuel.get();
163 fuel.set(remaining - 1);
164 if remaining == 0 {
165 log::debug!("fuel exhausted");
166 }
167 remaining > 0
168 };
169 let mut solve = || {
170 let solution = solver.solve_limited(&context, goal, should_continue);
171 log::debug!("solve({:?}) => {:?}", goal, solution);
172 solution
173 };
174 // don't set the TLS for Chalk unless Chalk debugging is active, to make
175 // extra sure we only use it for debugging
176 let solution =
177 if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() };
178
179 solution
180}
181
182fn is_chalk_debug() -> bool {
183 std::env::var("CHALK_DEBUG").is_ok()
184}
185
186fn solution_from_chalk(
187 db: &dyn HirDatabase,
188 solution: chalk_solve::Solution<Interner>,
189) -> Solution {
190 let convert_subst = |subst: chalk_ir::Canonical<chalk_ir::Substitution<Interner>>| {
191 let result = from_chalk(db, subst);
192 SolutionVariables(result)
193 };
194 match solution {
195 chalk_solve::Solution::Unique(constr_subst) => {
196 let subst = chalk_ir::Canonical {
197 value: constr_subst.value.subst,
198 binders: constr_subst.binders,
199 };
200 Solution::Unique(convert_subst(subst))
201 }
202 chalk_solve::Solution::Ambig(chalk_solve::Guidance::Definite(subst)) => {
203 Solution::Ambig(Guidance::Definite(convert_subst(subst)))
204 }
205 chalk_solve::Solution::Ambig(chalk_solve::Guidance::Suggested(subst)) => {
206 Solution::Ambig(Guidance::Suggested(convert_subst(subst)))
207 }
208 chalk_solve::Solution::Ambig(chalk_solve::Guidance::Unknown) => {
209 Solution::Ambig(Guidance::Unknown)
210 }
211 }
212}
213
214#[derive(Clone, Debug, PartialEq, Eq)]
215pub struct SolutionVariables(pub Canonical<Substs>);
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218/// A (possible) solution for a proposed goal.
219pub enum Solution {
220 /// The goal indeed holds, and there is a unique value for all existential
221 /// variables.
222 Unique(SolutionVariables),
223
224 /// The goal may be provable in multiple ways, but regardless we may have some guidance
225 /// for type inference. In this case, we don't return any lifetime
226 /// constraints, since we have not "committed" to any particular solution
227 /// yet.
228 Ambig(Guidance),
229}
230
231#[derive(Clone, Debug, PartialEq, Eq)]
232/// When a goal holds ambiguously (e.g., because there are multiple possible
233/// solutions), we issue a set of *guidance* back to type inference.
234pub enum Guidance {
235 /// The existential variables *must* have the given values if the goal is
236 /// ever to hold, but that alone isn't enough to guarantee the goal will
237 /// actually hold.
238 Definite(SolutionVariables),
239
240 /// There are multiple plausible values for the existentials, but the ones
241 /// here are suggested as the preferred choice heuristically. These should
242 /// be used for inference fallback only.
243 Suggested(SolutionVariables),
244
245 /// There's no useful information to feed back to type inference
246 Unknown,
247}
248
249#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
250pub enum FnTrait {
251 FnOnce,
252 FnMut,
253 Fn,
254}
255
256impl FnTrait {
257 fn lang_item_name(self) -> &'static str {
258 match self {
259 FnTrait::FnOnce => "fn_once",
260 FnTrait::FnMut => "fn_mut",
261 FnTrait::Fn => "fn",
262 }
263 }
264
265 pub fn get_id(&self, db: &dyn HirDatabase, krate: CrateId) -> Option<TraitId> {
266 let target = db.lang_item(krate, self.lang_item_name().into())?;
267 match target {
268 LangItemTarget::TraitId(t) => Some(t),
269 _ => None,
270 }
271 }
272}