1use std::{alloc, path::PathBuf, ptr, rc::Rc, slice};
2
3use flux_arc_interner::List;
4use flux_common::{bug, result::ErrorEmitter};
5use flux_config as config;
6use flux_errors::FluxSession;
7use flux_rustc_bridge::{self, lowering::Lower, mir, ty};
8use rustc_data_structures::unord::UnordSet;
9use rustc_hir::{
10 def::DefKind,
11 def_id::{CrateNum, DefId, LocalDefId},
12};
13use rustc_middle::{
14 query::IntoQueryParam,
15 ty::{TyCtxt, Variance},
16};
17use rustc_span::Span;
18pub use rustc_span::{Symbol, symbol::Ident};
19use tempfile::TempDir;
20
21use crate::{
22 cstore::CrateStoreDyn,
23 def_id::{FluxDefId, FluxLocalDefId, MaybeExternId, ResolvedDefId},
24 fhir::{self, VariantIdx},
25 queries::{Providers, Queries, QueryErr, QueryResult},
26 query_bug,
27 rty::{
28 self, QualifierKind,
29 refining::{Refine as _, Refiner},
30 },
31};
32
33#[derive(Clone, Copy)]
34pub struct GlobalEnv<'genv, 'tcx> {
35 inner: &'genv GlobalEnvInner<'genv, 'tcx>,
36}
37
38struct GlobalEnvInner<'genv, 'tcx> {
39 tcx: TyCtxt<'tcx>,
40 sess: &'genv FluxSession,
41 arena: &'genv fhir::Arena,
42 cstore: Box<CrateStoreDyn>,
43 queries: Queries<'genv, 'tcx>,
44 tempdir: TempDir,
45}
46
47impl<'tcx> GlobalEnv<'_, 'tcx> {
48 pub fn enter<'a, R>(
49 tcx: TyCtxt<'tcx>,
50 sess: &'a FluxSession,
51 cstore: Box<CrateStoreDyn>,
52 arena: &'a fhir::Arena,
53 providers: Providers,
54 f: impl for<'genv> FnOnce(GlobalEnv<'genv, 'tcx>) -> R,
55 ) -> R {
56 let tempdir = TempDir::new_in(lean_parent_dir(tcx)).unwrap();
59 let queries = Queries::new(providers);
60 let inner = GlobalEnvInner { tcx, sess, cstore, arena, queries, tempdir };
61 f(GlobalEnv { inner: &inner })
62 }
63}
64
65impl<'genv, 'tcx> GlobalEnv<'genv, 'tcx> {
66 pub fn tcx(self) -> TyCtxt<'tcx> {
67 self.inner.tcx
68 }
69
70 pub fn sess(self) -> &'genv FluxSession {
71 self.inner.sess
72 }
73
74 pub fn collect_specs(self) -> &'genv crate::Specs {
75 self.inner.queries.collect_specs(self)
76 }
77
78 pub fn resolve_crate(self) -> &'genv crate::ResolverOutput {
79 self.inner.queries.resolve_crate(self)
80 }
81
82 pub fn lean_parent_dir(self) -> PathBuf {
84 lean_parent_dir(self.tcx())
85 }
86
87 pub fn temp_dir(self) -> &'genv TempDir {
88 &self.inner.tempdir
89 }
90
91 pub fn desugar(self, def_id: LocalDefId) -> QueryResult<fhir::Node<'genv>> {
92 self.inner.queries.desugar(self, def_id)
93 }
94
95 pub fn fhir_attr_map(self, def_id: LocalDefId) -> fhir::AttrMap<'genv> {
96 self.inner.queries.fhir_attr_map(self, def_id)
97 }
98
99 pub fn fhir_crate(self) -> &'genv fhir::FluxItems<'genv> {
100 self.inner.queries.fhir_crate(self)
101 }
102
103 pub fn alloc<T>(&self, val: T) -> &'genv T {
104 self.inner.arena.alloc(val)
105 }
106
107 pub fn alloc_slice<T: Copy>(self, slice: &[T]) -> &'genv [T] {
108 self.inner.arena.alloc_slice_copy(slice)
109 }
110
111 pub fn alloc_slice_fill_iter<T, I>(self, it: I) -> &'genv [T]
112 where
113 I: IntoIterator<Item = T>,
114 I::IntoIter: ExactSizeIterator,
115 {
116 self.inner.arena.alloc_slice_fill_iter(it)
117 }
118
119 pub fn def_kind(&self, def_id: impl IntoQueryParam<DefId>) -> DefKind {
120 self.tcx().def_kind(def_id.into_query_param())
121 }
122
123 pub fn alloc_slice_with_capacity<T, I>(self, cap: usize, it: I) -> &'genv [T]
133 where
134 I: IntoIterator<Item = T>,
135 {
136 let layout = alloc::Layout::array::<T>(cap).unwrap_or_else(|_| bug!("out of memory"));
137 let dst = self.inner.arena.alloc_layout(layout).cast::<T>();
138 unsafe {
139 let mut len = 0;
140 for (i, v) in it.into_iter().take(cap).enumerate() {
141 len += 1;
142 ptr::write(dst.as_ptr().add(i), v);
143 }
144
145 slice::from_raw_parts(dst.as_ptr(), len)
146 }
147 }
148
149 pub fn normalized_info(self, did: FluxDefId) -> rty::NormalizeInfo {
150 self.normalized_defns(did.krate()).func_info(did).clone()
151 }
152
153 pub fn normalized_defns(self, krate: CrateNum) -> Rc<rty::NormalizedDefns> {
154 self.inner.queries.normalized_defns(self, krate)
155 }
156
157 pub fn prim_rel_for(self, op: &rty::BinOp) -> QueryResult<Option<&'genv rty::PrimRel>> {
158 Ok(self.inner.queries.prim_rel(self)?.get(op))
159 }
160
161 pub fn qualifiers(self) -> QueryResult<&'genv [rty::Qualifier]> {
162 self.inner.queries.qualifiers(self)
163 }
164
165 pub fn qualifiers_for(
167 self,
168 did: LocalDefId,
169 ) -> QueryResult<impl Iterator<Item = &'genv rty::Qualifier>> {
170 let quals = self.fhir_attr_map(did).qualifiers;
171 let names: UnordSet<_> = quals.iter().copied().collect();
172 Ok(self.qualifiers()?.iter().filter(move |qual| {
173 match qual.kind {
174 QualifierKind::Global => true,
175 QualifierKind::Hint => qual.def_id.parent() == did,
176 QualifierKind::Local => names.contains(&qual.def_id),
177 }
178 }))
179 }
180
181 pub fn reveals_for(self, did: LocalDefId) -> &'genv [FluxDefId] {
183 self.fhir_attr_map(did).reveals
184 }
185
186 pub fn func_sort(self, def_id: impl IntoQueryParam<FluxDefId>) -> rty::PolyFuncSort {
187 self.inner
188 .queries
189 .func_sort(self, def_id.into_query_param())
190 }
191
192 pub fn func_span(self, def_id: impl IntoQueryParam<FluxDefId>) -> Span {
193 self.inner
194 .queries
195 .func_span(self, def_id.into_query_param())
196 }
197
198 pub fn should_inline_fun(self, def_id: FluxDefId) -> bool {
199 let is_poly = self.func_sort(def_id).params().len() > 0;
200 is_poly || !flux_config::smt_define_fun()
201 }
202
203 pub fn variances_of(self, did: DefId) -> &'tcx [Variance] {
204 self.tcx().variances_of(did)
205 }
206
207 pub fn mir(self, def_id: LocalDefId) -> QueryResult<Rc<mir::BodyRoot<'tcx>>> {
208 self.inner.queries.mir(self, def_id)
209 }
210
211 pub fn lower_generics_of(self, def_id: impl IntoQueryParam<DefId>) -> ty::Generics<'tcx> {
212 self.inner
213 .queries
214 .lower_generics_of(self, def_id.into_query_param())
215 }
216
217 pub fn lower_predicates_of(
218 self,
219 def_id: impl IntoQueryParam<DefId>,
220 ) -> QueryResult<ty::GenericPredicates> {
221 self.inner
222 .queries
223 .lower_predicates_of(self, def_id.into_query_param())
224 }
225
226 pub fn lower_type_of(
227 self,
228 def_id: impl IntoQueryParam<DefId>,
229 ) -> QueryResult<ty::EarlyBinder<ty::Ty>> {
230 self.inner
231 .queries
232 .lower_type_of(self, def_id.into_query_param())
233 }
234
235 pub fn lower_fn_sig(
236 self,
237 def_id: impl Into<DefId>,
238 ) -> QueryResult<ty::EarlyBinder<ty::PolyFnSig>> {
239 self.inner.queries.lower_fn_sig(self, def_id.into())
240 }
241
242 pub fn adt_def(self, def_id: impl IntoQueryParam<DefId>) -> QueryResult<rty::AdtDef> {
243 self.inner.queries.adt_def(self, def_id.into_query_param())
244 }
245
246 pub fn constant_info(
247 self,
248 def_id: impl IntoQueryParam<DefId>,
249 ) -> QueryResult<rty::ConstantInfo> {
250 self.inner
251 .queries
252 .constant_info(self, def_id.into_query_param())
253 }
254
255 pub fn adt_sort_def_of(
256 self,
257 def_id: impl IntoQueryParam<DefId>,
258 ) -> QueryResult<rty::AdtSortDef> {
259 self.inner
260 .queries
261 .adt_sort_def_of(self, def_id.into_query_param())
262 }
263
264 pub fn sort_decl_param_count(self, def_id: impl IntoQueryParam<FluxDefId>) -> usize {
265 self.inner
266 .queries
267 .sort_decl_param_count(self, def_id.into_query_param())
268 }
269
270 pub fn check_wf(self, def_id: LocalDefId) -> QueryResult<Rc<rty::WfckResults>> {
271 self.inner.queries.check_wf(self, def_id)
272 }
273
274 pub fn impl_trait_ref(
275 self,
276 impl_id: DefId,
277 ) -> QueryResult<Option<rty::EarlyBinder<rty::TraitRef>>> {
278 let Some(trait_ref) = self.tcx().impl_trait_ref(impl_id) else { return Ok(None) };
279 let trait_ref = trait_ref.skip_binder();
280 let trait_ref = trait_ref
281 .lower(self.tcx())
282 .map_err(|err| QueryErr::unsupported(trait_ref.def_id, err.into_err()))?
283 .refine(&Refiner::default_for_item(self, impl_id)?)?;
284 Ok(Some(rty::EarlyBinder(trait_ref)))
285 }
286
287 pub fn generics_of(self, def_id: impl IntoQueryParam<DefId>) -> QueryResult<rty::Generics> {
288 self.inner
289 .queries
290 .generics_of(self, def_id.into_query_param())
291 }
292
293 pub fn refinement_generics_of(
294 self,
295 def_id: impl IntoQueryParam<DefId>,
296 ) -> QueryResult<rty::EarlyBinder<rty::RefinementGenerics>> {
297 self.inner
298 .queries
299 .refinement_generics_of(self, def_id.into_query_param())
300 }
301
302 pub fn predicates_of(
303 self,
304 def_id: impl IntoQueryParam<DefId>,
305 ) -> QueryResult<rty::EarlyBinder<rty::GenericPredicates>> {
306 self.inner
307 .queries
308 .predicates_of(self, def_id.into_query_param())
309 }
310
311 pub fn assoc_refinements_of(
312 self,
313 def_id: impl IntoQueryParam<DefId>,
314 ) -> QueryResult<rty::AssocRefinements> {
315 self.inner
316 .queries
317 .assoc_refinements_of(self, def_id.into_query_param())
318 }
319
320 pub fn assoc_refinement(self, assoc_id: FluxDefId) -> QueryResult<rty::AssocReft> {
321 Ok(self.assoc_refinements_of(assoc_id.parent())?.get(assoc_id))
322 }
323
324 pub fn assoc_refinement_body_for_impl(
332 self,
333 trait_assoc_id: FluxDefId,
334 impl_id: DefId,
335 ) -> QueryResult<rty::EarlyBinder<rty::Lambda>> {
336 let impl_assoc_refts = self.assoc_refinements_of(impl_id)?;
338 if let Some(impl_assoc_reft) = impl_assoc_refts.find(trait_assoc_id.name()) {
339 return self.assoc_refinement_body(impl_assoc_reft.def_id());
340 }
341
342 if let Some(body) = self.default_assoc_refinement_body(trait_assoc_id)? {
344 let impl_trait_ref = self
345 .impl_trait_ref(impl_id)?
346 .unwrap()
347 .instantiate_identity();
348 return Ok(rty::EarlyBinder(body.instantiate(self.tcx(), &impl_trait_ref.args, &[])));
349 }
350
351 Err(QueryErr::MissingAssocReft {
352 impl_id,
353 trait_id: trait_assoc_id.parent(),
354 name: trait_assoc_id.name(),
355 })
356 }
357
358 pub fn default_assoc_refinement_body(
359 self,
360 trait_assoc_id: FluxDefId,
361 ) -> QueryResult<Option<rty::EarlyBinder<rty::Lambda>>> {
362 self.inner
363 .queries
364 .default_assoc_refinement_body(self, trait_assoc_id)
365 }
366
367 pub fn assoc_refinement_body(
368 self,
369 impl_assoc_id: FluxDefId,
370 ) -> QueryResult<rty::EarlyBinder<rty::Lambda>> {
371 self.inner
372 .queries
373 .assoc_refinement_body(self, impl_assoc_id)
374 }
375
376 pub fn sort_of_assoc_reft(
377 self,
378 assoc_id: FluxDefId,
379 ) -> QueryResult<rty::EarlyBinder<rty::FuncSort>> {
380 self.inner.queries.sort_of_assoc_reft(self, assoc_id)
381 }
382
383 pub fn item_bounds(self, def_id: DefId) -> QueryResult<rty::EarlyBinder<List<rty::Clause>>> {
384 self.inner.queries.item_bounds(self, def_id)
385 }
386
387 pub fn type_of(
388 self,
389 def_id: impl IntoQueryParam<DefId>,
390 ) -> QueryResult<rty::EarlyBinder<rty::TyOrCtor>> {
391 self.inner.queries.type_of(self, def_id.into_query_param())
392 }
393
394 pub fn fn_sig(
395 self,
396 def_id: impl IntoQueryParam<DefId>,
397 ) -> QueryResult<rty::EarlyBinder<rty::PolyFnSig>> {
398 self.inner.queries.fn_sig(self, def_id.into_query_param())
399 }
400
401 pub fn variants_of(
402 self,
403 def_id: impl IntoQueryParam<DefId>,
404 ) -> QueryResult<rty::Opaqueness<rty::EarlyBinder<rty::PolyVariants>>> {
405 self.inner
406 .queries
407 .variants_of(self, def_id.into_query_param())
408 }
409
410 pub fn variant_sig(
411 self,
412 def_id: DefId,
413 variant_idx: VariantIdx,
414 ) -> QueryResult<rty::Opaqueness<rty::EarlyBinder<rty::PolyVariant>>> {
415 Ok(self
416 .variants_of(def_id)?
417 .map(|variants| variants.map(|variants| variants[variant_idx.as_usize()].clone())))
418 }
419
420 pub fn lower_late_bound_vars(
421 self,
422 def_id: LocalDefId,
423 ) -> QueryResult<List<ty::BoundVariableKind>> {
424 self.inner.queries.lower_late_bound_vars(self, def_id)
425 }
426
427 pub fn no_panic(self, def_id: impl IntoQueryParam<DefId>) -> bool {
429 self.inner.queries.no_panic(self, def_id.into_query_param())
430 }
431
432 pub fn is_box(&self, res: fhir::Res) -> bool {
433 res.is_box(self.tcx())
434 }
435
436 pub fn def_id_to_param_index(&self, def_id: DefId) -> u32 {
437 let parent = self.tcx().parent(def_id);
438 let generics = self.tcx().generics_of(parent);
439 generics.param_def_id_to_index(self.tcx(), def_id).unwrap()
440 }
441
442 pub(crate) fn cstore(self) -> &'genv CrateStoreDyn {
443 &*self.inner.cstore
444 }
445
446 pub fn has_trusted_impl(&self, def_id: DefId) -> bool {
447 if let Some(did) = self
448 .resolve_id(def_id)
449 .as_maybe_extern()
450 .map(|id| id.local_id())
451 {
452 self.trusted_impl(did)
453 } else {
454 false
455 }
456 }
457
458 pub fn is_fn_output(&self, def_id: DefId) -> bool {
462 let def_span = self.tcx().def_span(def_id);
463 self.tcx()
464 .require_lang_item(rustc_hir::LangItem::FnOnceOutput, def_span)
465 == def_id
466 }
467
468 pub fn iter_local_def_id(self) -> impl Iterator<Item = LocalDefId> + use<'tcx, 'genv> {
470 self.tcx().iter_local_def_id().filter(move |&local_def_id| {
471 self.maybe_extern_id(local_def_id).is_local() && !self.is_dummy(local_def_id)
472 })
473 }
474
475 pub fn iter_extern_def_id(self) -> impl Iterator<Item = DefId> + use<'tcx, 'genv> {
476 self.tcx()
477 .iter_local_def_id()
478 .filter_map(move |local_def_id| self.maybe_extern_id(local_def_id).as_extern())
479 }
480
481 pub fn maybe_extern_id(self, local_id: LocalDefId) -> MaybeExternId {
482 self.collect_specs()
483 .local_id_to_extern_id
484 .get(&local_id)
485 .map_or_else(
486 || MaybeExternId::Local(local_id),
487 |def_id| MaybeExternId::Extern(local_id, *def_id),
488 )
489 }
490
491 #[expect(clippy::disallowed_methods)]
492 pub fn resolve_id(self, def_id: DefId) -> ResolvedDefId {
493 let maybe_extern_spec = self
494 .collect_specs()
495 .extern_id_to_local_id
496 .get(&def_id)
497 .copied();
498 if let Some(local_id) = maybe_extern_spec {
499 ResolvedDefId::ExternSpec(local_id, def_id)
500 } else if let Some(local_id) = def_id.as_local() {
501 debug_assert!(
502 self.maybe_extern_id(local_id).is_local(),
503 "def id points to dummy local item `{def_id:?}`"
504 );
505 ResolvedDefId::Local(local_id)
506 } else {
507 ResolvedDefId::Extern(def_id)
508 }
509 }
510
511 pub fn infer_opts(self, def_id: LocalDefId) -> config::InferOpts {
512 let mut opts = config::PartialInferOpts::default();
513 self.traverse_parents(def_id, |did| {
514 if let Some(o) = self.fhir_attr_map(did).infer_opts() {
515 opts.merge(&o);
516 }
517 None::<!>
518 });
519 opts.into()
520 }
521
522 pub fn trusted(self, def_id: LocalDefId) -> bool {
526 self.traverse_parents(def_id, |did| self.fhir_attr_map(did).trusted())
527 .map(|trusted| trusted.to_bool())
528 .unwrap_or_else(config::trusted_default)
529 }
530
531 pub fn trusted_impl(self, def_id: LocalDefId) -> bool {
532 self.traverse_parents(def_id, |did| self.fhir_attr_map(did).trusted_impl())
533 .map(|trusted| trusted.to_bool())
534 .unwrap_or(false)
535 }
536
537 pub fn is_dummy(self, def_id: LocalDefId) -> bool {
541 self.traverse_parents(def_id, |did| {
542 self.collect_specs()
543 .dummy_extern
544 .contains(&did)
545 .then_some(())
546 })
547 .is_some()
548 }
549
550 pub fn ignored(self, def_id: LocalDefId) -> bool {
554 self.traverse_parents(def_id, |did| self.fhir_attr_map(did).ignored())
555 .map(|ignored| ignored.to_bool())
556 .unwrap_or_else(config::ignore_default)
557 }
558
559 pub fn should_fail(self, def_id: LocalDefId) -> bool {
561 self.fhir_attr_map(def_id).should_fail()
562 }
563
564 pub fn proven_externally(self, def_id: LocalDefId) -> Option<Span> {
566 self.fhir_attr_map(def_id).proven_externally()
567 }
568
569 fn traverse_parents<T>(
571 self,
572 mut def_id: LocalDefId,
573 mut f: impl FnMut(LocalDefId) -> Option<T>,
574 ) -> Option<T> {
575 loop {
576 if let Some(v) = f(def_id) {
577 break Some(v);
578 }
579
580 if let Some(parent) = self.tcx().opt_local_parent(def_id) {
581 def_id = parent;
582 } else {
583 break None;
584 }
585 }
586 }
587}
588
589impl<'genv, 'tcx> GlobalEnv<'genv, 'tcx> {
590 pub fn fhir_iter_flux_items(
591 self,
592 ) -> impl Iterator<Item = (FluxLocalDefId, fhir::FluxItem<'genv>)> {
593 self.fhir_crate()
594 .items
595 .iter()
596 .map(|(id, item)| (*id, *item))
597 }
598
599 pub fn fhir_sort_decl(&self, def_id: FluxLocalDefId) -> Option<&fhir::SortDecl> {
600 self.fhir_crate().items.get(&def_id).and_then(|item| {
601 if let fhir::FluxItem::SortDecl(sort_decl) = item { Some(*sort_decl) } else { None }
602 })
603 }
604
605 pub fn fhir_spec_func_body(
606 &self,
607 def_id: FluxLocalDefId,
608 ) -> Option<&'genv fhir::SpecFunc<'genv>> {
609 self.fhir_crate()
610 .items
611 .get(&def_id)
612 .and_then(|item| if let fhir::FluxItem::Func(defn) = item { Some(*defn) } else { None })
613 }
614
615 pub fn fhir_qualifiers(self) -> impl Iterator<Item = &'genv fhir::Qualifier<'genv>> {
616 self.fhir_crate().items.values().filter_map(|item| {
617 if let fhir::FluxItem::Qualifier(qual) = item { Some(*qual) } else { None }
618 })
619 }
620
621 pub fn fhir_primop_props(self) -> impl Iterator<Item = &'genv fhir::PrimOpProp<'genv>> {
622 self.fhir_crate().items.values().filter_map(|item| {
623 if let fhir::FluxItem::PrimOpProp(prop) = item { Some(*prop) } else { None }
624 })
625 }
626
627 pub fn fhir_get_generics(
628 self,
629 def_id: LocalDefId,
630 ) -> QueryResult<Option<&'genv fhir::Generics<'genv>>> {
631 if matches!(self.def_kind(def_id), DefKind::Closure) {
633 Ok(None)
634 } else {
635 Ok(Some(self.fhir_expect_owner_node(def_id)?.generics()))
636 }
637 }
638
639 pub fn fhir_expect_refinement_kind(
640 self,
641 def_id: LocalDefId,
642 ) -> QueryResult<&'genv fhir::RefinementKind<'genv>> {
643 let kind = match &self.fhir_expect_item(def_id)?.kind {
644 fhir::ItemKind::Enum(enum_def) => &enum_def.refinement,
645 fhir::ItemKind::Struct(struct_def) => &struct_def.refinement,
646 _ => bug!("expected struct, enum or type alias"),
647 };
648 Ok(kind)
649 }
650
651 pub fn fhir_expect_item(self, def_id: LocalDefId) -> QueryResult<&'genv fhir::Item<'genv>> {
652 if let fhir::Node::Item(item) = self.fhir_node(def_id)? {
653 Ok(item)
654 } else {
655 Err(query_bug!(def_id, "expected item: `{def_id:?}`"))
656 }
657 }
658
659 pub fn fhir_expect_owner_node(self, def_id: LocalDefId) -> QueryResult<fhir::OwnerNode<'genv>> {
660 let Some(owner) = self.fhir_node(def_id)?.as_owner() else {
661 return Err(query_bug!(def_id, "cannot find owner node"));
662 };
663 Ok(owner)
664 }
665
666 pub fn fhir_node(self, def_id: LocalDefId) -> QueryResult<fhir::Node<'genv>> {
667 self.desugar(def_id)
668 }
669}
670
671#[macro_export]
672macro_rules! try_alloc_slice {
673 ($genv:expr, $slice:expr, $map:expr $(,)?) => {{
674 let slice = $slice;
675 $crate::try_alloc_slice!($genv, cap: slice.len(), slice.into_iter().map($map))
676 }};
677 ($genv:expr, cap: $cap:expr, $it:expr $(,)?) => {{
678 let mut err = None;
679 let slice = $genv.alloc_slice_with_capacity($cap, $it.into_iter().collect_errors(&mut err));
680 err.map_or(Ok(slice), Err)
681 }};
682}
683
684impl ErrorEmitter for GlobalEnv<'_, '_> {
685 fn emit<'a>(&'a self, err: impl rustc_errors::Diagnostic<'a>) -> rustc_span::ErrorGuaranteed {
686 self.sess().emit(err)
687 }
688}
689
690fn lean_parent_dir(tcx: TyCtxt) -> PathBuf {
691 tcx.sess
692 .opts
693 .working_dir
694 .local_path_if_available()
695 .to_path_buf()
696 .join(config::lean_dir())
697}