diff options
Diffstat (limited to 'crates/hir_ty/src/traits.rs')
-rw-r--r-- | crates/hir_ty/src/traits.rs | 272 |
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. | ||
2 | use std::sync::Arc; | ||
3 | |||
4 | use base_db::CrateId; | ||
5 | use chalk_ir::cast::Cast; | ||
6 | use chalk_solve::Solver; | ||
7 | use hir_def::{lang_item::LangItemTarget, TraitId}; | ||
8 | |||
9 | use crate::{db::HirDatabase, DebruijnIndex, Substs}; | ||
10 | |||
11 | use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; | ||
12 | |||
13 | use self::chalk::{from_chalk, Interner, ToChalk}; | ||
14 | |||
15 | pub(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. | ||
24 | const CHALK_SOLVER_FUEL: i32 = 100; | ||
25 | |||
26 | #[derive(Debug, Copy, Clone)] | ||
27 | struct ChalkContext<'a> { | ||
28 | db: &'a dyn HirDatabase, | ||
29 | krate: CrateId, | ||
30 | } | ||
31 | |||
32 | fn 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)] | ||
44 | pub struct TraitEnvironment { | ||
45 | pub predicates: Vec<GenericPredicate>, | ||
46 | } | ||
47 | |||
48 | impl 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)] | ||
65 | pub struct InEnvironment<T> { | ||
66 | pub environment: Arc<TraitEnvironment>, | ||
67 | pub value: T, | ||
68 | } | ||
69 | |||
70 | impl<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)] | ||
80 | pub 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 | |||
87 | impl 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)] | ||
100 | pub struct ProjectionPredicate { | ||
101 | pub projection_ty: ProjectionTy, | ||
102 | pub ty: Ty, | ||
103 | } | ||
104 | |||
105 | impl 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. | ||
122 | pub(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 | |||
149 | fn 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 | |||
182 | fn is_chalk_debug() -> bool { | ||
183 | std::env::var("CHALK_DEBUG").is_ok() | ||
184 | } | ||
185 | |||
186 | fn 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)] | ||
215 | pub struct SolutionVariables(pub Canonical<Substs>); | ||
216 | |||
217 | #[derive(Clone, Debug, PartialEq, Eq)] | ||
218 | /// A (possible) solution for a proposed goal. | ||
219 | pub 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. | ||
234 | pub 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)] | ||
250 | pub enum FnTrait { | ||
251 | FnOnce, | ||
252 | FnMut, | ||
253 | Fn, | ||
254 | } | ||
255 | |||
256 | impl 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 | } | ||