]> git.lizzy.rs Git - rust.git/blob - src/librustc_ast_borrowck/borrowck/mod.rs
Move the HIR cfg to `rustc_ast_borrowck`
[rust.git] / src / librustc_ast_borrowck / borrowck / mod.rs
1 //! See The Book chapter on the borrow checker for more details.
2
3 #![allow(non_camel_case_types)]
4
5 pub use LoanPathKind::*;
6 pub use LoanPathElem::*;
7
8 use InteriorKind::*;
9
10 use rustc::hir::HirId;
11 use rustc::hir::Node;
12 use rustc::middle::borrowck::{BorrowCheckResult, SignalledError};
13 use rustc::hir::def_id::{DefId, LocalDefId};
14 use rustc::middle::mem_categorization as mc;
15 use rustc::middle::mem_categorization::Categorization;
16 use rustc::middle::region;
17 use rustc::middle::free_region::RegionRelations;
18 use rustc::ty::{self, Ty, TyCtxt};
19 use rustc::ty::query::Providers;
20
21 use std::borrow::Cow;
22 use std::cell::{Cell};
23 use std::fmt;
24 use std::rc::Rc;
25 use std::hash::{Hash, Hasher};
26 use log::debug;
27
28 use rustc::hir;
29
30 use crate::cfg;
31 use crate::dataflow::{DataFlowContext, BitwiseOperator, DataFlowOperator, KillFrom};
32
33 pub mod check_loans;
34
35 pub mod gather_loans;
36
37 pub mod move_data;
38
39 #[derive(Clone, Copy)]
40 pub struct LoanDataFlowOperator;
41
42 pub type LoanDataFlow<'tcx> = DataFlowContext<'tcx, LoanDataFlowOperator>;
43
44 pub fn check_crate(tcx: TyCtxt<'_>) {
45     tcx.par_body_owners(|body_owner_def_id| {
46         tcx.ensure().borrowck(body_owner_def_id);
47     });
48 }
49
50 pub fn provide(providers: &mut Providers<'_>) {
51     *providers = Providers {
52         borrowck,
53         ..*providers
54     };
55 }
56
57 /// Collection of conclusions determined via borrow checker analyses.
58 pub struct AnalysisData<'tcx> {
59     pub all_loans: Vec<Loan<'tcx>>,
60     pub loans: DataFlowContext<'tcx, LoanDataFlowOperator>,
61     pub move_data: move_data::FlowedMoveData<'tcx>,
62 }
63
64 fn borrowck(tcx: TyCtxt<'_>, owner_def_id: DefId) -> &BorrowCheckResult {
65     assert!(tcx.use_ast_borrowck() || tcx.migrate_borrowck());
66
67     debug!("borrowck(body_owner_def_id={:?})", owner_def_id);
68
69     let signalled_error = tcx.check_match(owner_def_id);
70     if let SignalledError::SawSomeError = signalled_error {
71         return tcx.arena.alloc(BorrowCheckResult {
72             signalled_any_error: SignalledError::SawSomeError,
73         })
74     }
75
76     let owner_id = tcx.hir().as_local_hir_id(owner_def_id).unwrap();
77
78     match tcx.hir().get(owner_id) {
79         Node::Ctor(..) => {
80             // We get invoked with anything that has MIR, but some of
81             // those things (notably the synthesized constructors from
82             // tuple structs/variants) do not have an associated body
83             // and do not need borrowchecking.
84             return tcx.arena.alloc(BorrowCheckResult {
85                 signalled_any_error: SignalledError::NoErrorsSeen,
86             })
87         }
88         _ => { }
89     }
90
91     let body_id = tcx.hir().body_owned_by(owner_id);
92     let tables = tcx.typeck_tables_of(owner_def_id);
93     let region_scope_tree = tcx.region_scope_tree(owner_def_id);
94     let body = tcx.hir().body(body_id);
95     let mut bccx = BorrowckCtxt {
96         tcx,
97         tables,
98         region_scope_tree,
99         owner_def_id,
100         body,
101         signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
102     };
103
104     // Eventually, borrowck will always read the MIR, but at the
105     // moment we do not. So, for now, we always force MIR to be
106     // constructed for a given fn, since this may result in errors
107     // being reported and we want that to happen.
108     //
109     // Note that `mir_validated` is a "stealable" result; the
110     // thief, `optimized_mir()`, forces borrowck, so we know that
111     // is not yet stolen.
112     tcx.ensure().mir_validated(owner_def_id);
113
114     // option dance because you can't capture an uninitialized variable
115     // by mut-ref.
116     let mut cfg = None;
117     if let Some(AnalysisData { all_loans,
118                                loans: loan_dfcx,
119                                move_data: flowed_moves }) =
120         build_borrowck_dataflow_data(&mut bccx, false, body_id,
121                                      |bccx| {
122                                          cfg = Some(cfg::CFG::new(bccx.tcx, &body));
123                                          cfg.as_mut().unwrap()
124                                      })
125     {
126         check_loans::check_loans(&mut bccx, &loan_dfcx, &flowed_moves, &all_loans, body);
127     }
128
129     tcx.arena.alloc(BorrowCheckResult {
130         signalled_any_error: bccx.signalled_any_error.into_inner(),
131     })
132 }
133
134 fn build_borrowck_dataflow_data<'a, 'c, 'tcx, F>(this: &mut BorrowckCtxt<'a, 'tcx>,
135                                                  force_analysis: bool,
136                                                  body_id: hir::BodyId,
137                                                  get_cfg: F)
138                                                  -> Option<AnalysisData<'tcx>>
139     where F: FnOnce(&mut BorrowckCtxt<'a, 'tcx>) -> &'c cfg::CFG
140 {
141     // Check the body of fn items.
142     let (all_loans, move_data) =
143         gather_loans::gather_loans_in_fn(this, body_id);
144
145     if !force_analysis && move_data.is_empty() && all_loans.is_empty() {
146         // large arrays of data inserted as constants can take a lot of
147         // time and memory to borrow-check - see issue #36799. However,
148         // they don't have places, so no borrow-check is actually needed.
149         // Recognize that case and skip borrow-checking.
150         debug!("skipping loan propagation for {:?} because of no loans", body_id);
151         return None;
152     } else {
153         debug!("propagating loans in {:?}", body_id);
154     }
155
156     let cfg = get_cfg(this);
157     let mut loan_dfcx =
158         DataFlowContext::new(this.tcx,
159                              "borrowck",
160                              Some(this.body),
161                              cfg,
162                              LoanDataFlowOperator,
163                              all_loans.len());
164     for (loan_idx, loan) in all_loans.iter().enumerate() {
165         loan_dfcx.add_gen(loan.gen_scope.item_local_id(), loan_idx);
166         loan_dfcx.add_kill(KillFrom::ScopeEnd,
167                            loan.kill_scope.item_local_id(),
168                            loan_idx);
169     }
170     loan_dfcx.add_kills_from_flow_exits(cfg);
171     loan_dfcx.propagate(cfg, this.body);
172
173     let flowed_moves = move_data::FlowedMoveData::new(move_data,
174                                                       this,
175                                                       cfg,
176                                                       this.body);
177
178     Some(AnalysisData { all_loans,
179                         loans: loan_dfcx,
180                         move_data:flowed_moves })
181 }
182
183 /// Accessor for introspective clients inspecting `AnalysisData` and
184 /// the `BorrowckCtxt` itself , e.g., the flowgraph visualizer.
185 pub fn build_borrowck_dataflow_data_for_fn<'a, 'tcx>(
186     tcx: TyCtxt<'tcx>,
187     body_id: hir::BodyId,
188     cfg: &cfg::CFG)
189     -> (BorrowckCtxt<'a, 'tcx>, AnalysisData<'tcx>)
190 {
191     let owner_id = tcx.hir().body_owner(body_id);
192     let owner_def_id = tcx.hir().local_def_id(owner_id);
193     let tables = tcx.typeck_tables_of(owner_def_id);
194     let region_scope_tree = tcx.region_scope_tree(owner_def_id);
195     let body = tcx.hir().body(body_id);
196     let mut bccx = BorrowckCtxt {
197         tcx,
198         tables,
199         region_scope_tree,
200         owner_def_id,
201         body,
202         signalled_any_error: Cell::new(SignalledError::NoErrorsSeen),
203     };
204
205     let dataflow_data = build_borrowck_dataflow_data(&mut bccx, true, body_id, |_| cfg);
206     (bccx, dataflow_data.unwrap())
207 }
208
209 // ----------------------------------------------------------------------
210 // Type definitions
211
212 pub struct BorrowckCtxt<'a, 'tcx> {
213     tcx: TyCtxt<'tcx>,
214
215     // tables for the current thing we are checking; set to
216     // Some in `borrowck_fn` and cleared later
217     tables: &'a ty::TypeckTables<'tcx>,
218
219     region_scope_tree: &'tcx region::ScopeTree,
220
221     owner_def_id: DefId,
222
223     body: &'tcx hir::Body,
224
225     signalled_any_error: Cell<SignalledError>,
226 }
227
228
229 impl<'a, 'tcx: 'a> BorrowckCtxt<'a, 'tcx> {
230     fn signal_error(&self) {
231         self.signalled_any_error.set(SignalledError::SawSomeError);
232     }
233 }
234
235 ///////////////////////////////////////////////////////////////////////////
236 // Loans and loan paths
237
238 /// Record of a loan that was issued.
239 pub struct Loan<'tcx> {
240     index: usize,
241     loan_path: Rc<LoanPath<'tcx>>,
242     kind: ty::BorrowKind,
243     restricted_paths: Vec<Rc<LoanPath<'tcx>>>,
244
245     /// gen_scope indicates where loan is introduced. Typically the
246     /// loan is introduced at the point of the borrow, but in some
247     /// cases, notably method arguments, the loan may be introduced
248     /// only later, once it comes into scope. See also
249     /// `GatherLoanCtxt::compute_gen_scope`.
250     gen_scope: region::Scope,
251
252     /// kill_scope indicates when the loan goes out of scope. This is
253     /// either when the lifetime expires or when the local variable
254     /// which roots the loan-path goes out of scope, whichever happens
255     /// faster. See also `GatherLoanCtxt::compute_kill_scope`.
256     kill_scope: region::Scope,
257 }
258
259 impl<'tcx> Loan<'tcx> {
260     pub fn loan_path(&self) -> Rc<LoanPath<'tcx>> {
261         self.loan_path.clone()
262     }
263 }
264
265 #[derive(Eq)]
266 pub struct LoanPath<'tcx> {
267     kind: LoanPathKind<'tcx>,
268     ty: Ty<'tcx>,
269 }
270
271 impl<'tcx> PartialEq for LoanPath<'tcx> {
272     fn eq(&self, that: &LoanPath<'tcx>) -> bool {
273         self.kind == that.kind
274     }
275 }
276
277 impl<'tcx> Hash for LoanPath<'tcx> {
278     fn hash<H: Hasher>(&self, state: &mut H) {
279         self.kind.hash(state);
280     }
281 }
282
283 #[derive(PartialEq, Eq, Hash, Debug)]
284 pub enum LoanPathKind<'tcx> {
285     LpVar(hir::HirId),                          // `x` in README.md
286     LpUpvar(ty::UpvarId),                       // `x` captured by-value into closure
287     LpDowncast(Rc<LoanPath<'tcx>>, DefId), // `x` downcast to particular enum variant
288     LpExtend(Rc<LoanPath<'tcx>>, mc::MutabilityCategory, LoanPathElem<'tcx>)
289 }
290
291 impl<'tcx> LoanPath<'tcx> {
292     fn new(kind: LoanPathKind<'tcx>, ty: Ty<'tcx>) -> LoanPath<'tcx> {
293         LoanPath { kind: kind, ty: ty }
294     }
295
296     fn to_type(&self) -> Ty<'tcx> { self.ty }
297 }
298
299 // FIXME (pnkfelix): See discussion here
300 // https://github.com/pnkfelix/rust/commit/
301 //     b2b39e8700e37ad32b486b9a8409b50a8a53aa51#commitcomment-7892003
302 const DOWNCAST_PRINTED_OPERATOR: &'static str = " as ";
303
304 // A local, "cleaned" version of `mc::InteriorKind` that drops
305 // information that is not relevant to loan-path analysis. (In
306 // particular, the distinction between how precisely an array-element
307 // is tracked is irrelevant here.)
308 #[derive(Clone, Copy, PartialEq, Eq, Hash)]
309 pub enum InteriorKind {
310     InteriorField(mc::FieldIndex),
311     InteriorElement,
312 }
313
314 trait ToInteriorKind { fn cleaned(self) -> InteriorKind; }
315 impl ToInteriorKind for mc::InteriorKind {
316     fn cleaned(self) -> InteriorKind {
317         match self {
318             mc::InteriorField(name) => InteriorField(name),
319             mc::InteriorElement(_) => InteriorElement,
320         }
321     }
322 }
323
324 // This can be:
325 // - a pointer dereference (`*P` in README.md)
326 // - a field reference, with an optional definition of the containing
327 //   enum variant (`P.f` in README.md)
328 // `DefId` is present when the field is part of struct that is in
329 // a variant of an enum. For instance in:
330 // `enum E { X { foo: u32 }, Y { foo: u32 }}`
331 // each `foo` is qualified by the definitition id of the variant (`X` or `Y`).
332 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
333 pub enum LoanPathElem<'tcx> {
334     LpDeref(mc::PointerKind<'tcx>),
335     LpInterior(Option<DefId>, InteriorKind),
336 }
337
338 fn closure_to_block(closure_id: LocalDefId, tcx: TyCtxt<'_>) -> HirId {
339     let closure_id = tcx.hir().local_def_id_to_hir_id(closure_id);
340     match tcx.hir().get(closure_id) {
341         Node::Expr(expr) => match expr.node {
342             hir::ExprKind::Closure(.., body_id, _, _) => {
343                 body_id.hir_id
344             }
345             _ => {
346                 bug!("encountered non-closure id: {}", closure_id)
347             }
348         },
349         _ => bug!("encountered non-expr id: {}", closure_id)
350     }
351 }
352
353 impl<'a, 'tcx> LoanPath<'tcx> {
354     pub fn kill_scope(&self, bccx: &BorrowckCtxt<'a, 'tcx>) -> region::Scope {
355         match self.kind {
356             LpVar(hir_id) => {
357                 bccx.region_scope_tree.var_scope(hir_id.local_id)
358             }
359             LpUpvar(upvar_id) => {
360                 let block_id = closure_to_block(upvar_id.closure_expr_id, bccx.tcx);
361                 region::Scope { id: block_id.local_id, data: region::ScopeData::Node }
362             }
363             LpDowncast(ref base, _) |
364             LpExtend(ref base, ..) => base.kill_scope(bccx),
365         }
366     }
367 }
368
369 // Avoid "cannot borrow immutable field `self.x` as mutable" as that implies that a field *can* be
370 // mutable independently of the struct it belongs to. (#35937)
371 pub fn opt_loan_path_is_field<'tcx>(cmt: &mc::cmt_<'tcx>) -> (Option<Rc<LoanPath<'tcx>>>, bool) {
372     let new_lp = |v: LoanPathKind<'tcx>| Rc::new(LoanPath::new(v, cmt.ty));
373
374     match cmt.cat {
375         Categorization::Rvalue(..) |
376         Categorization::ThreadLocal(..) |
377         Categorization::StaticItem => {
378             (None, false)
379         }
380
381         Categorization::Local(id) => {
382             (Some(new_lp(LpVar(id))), false)
383         }
384
385         Categorization::Upvar(mc::Upvar { id, .. }) => {
386             (Some(new_lp(LpUpvar(id))), false)
387         }
388
389         Categorization::Deref(ref cmt_base, pk) => {
390             let lp = opt_loan_path_is_field(cmt_base);
391             (lp.0.map(|lp| {
392                 new_lp(LpExtend(lp, cmt.mutbl, LpDeref(pk)))
393             }), lp.1)
394         }
395
396         Categorization::Interior(ref cmt_base, ik) => {
397             (opt_loan_path(cmt_base).map(|lp| {
398                 let opt_variant_id = match cmt_base.cat {
399                     Categorization::Downcast(_, did) =>  Some(did),
400                     _ => None
401                 };
402                 new_lp(LpExtend(lp, cmt.mutbl, LpInterior(opt_variant_id, ik.cleaned())))
403             }), true)
404         }
405
406         Categorization::Downcast(ref cmt_base, variant_def_id) => {
407             let lp = opt_loan_path_is_field(cmt_base);
408             (lp.0.map(|lp| {
409                 new_lp(LpDowncast(lp, variant_def_id))
410             }), lp.1)
411         }
412     }
413 }
414
415 /// Computes the `LoanPath` (if any) for a `cmt`.
416 /// Note that this logic is somewhat duplicated in
417 /// the method `compute()` found in `gather_loans::restrictions`,
418 /// which allows it to share common loan path pieces as it
419 /// traverses the CMT.
420 pub fn opt_loan_path<'tcx>(cmt: &mc::cmt_<'tcx>) -> Option<Rc<LoanPath<'tcx>>> {
421     opt_loan_path_is_field(cmt).0
422 }
423
424 ///////////////////////////////////////////////////////////////////////////
425 // Misc
426
427 impl<'a, 'tcx> BorrowckCtxt<'a, 'tcx> {
428     pub fn is_subregion_of(&self,
429                            r_sub: ty::Region<'tcx>,
430                            r_sup: ty::Region<'tcx>)
431                            -> bool
432     {
433         let region_rels = RegionRelations::new(self.tcx,
434                                                self.owner_def_id,
435                                                &self.region_scope_tree,
436                                                &self.tables.free_region_map);
437         region_rels.is_subregion_of(r_sub, r_sup)
438     }
439
440     pub fn append_loan_path_to_string(&self,
441                                       loan_path: &LoanPath<'tcx>,
442                                       out: &mut String) {
443         match loan_path.kind {
444             LpUpvar(ty::UpvarId { var_path: ty::UpvarPath { hir_id: id }, closure_expr_id: _ }) => {
445                 out.push_str(&self.tcx.hir().name(id).as_str());
446             }
447             LpVar(id) => {
448                 out.push_str(&self.tcx.hir().name(id).as_str());
449             }
450
451             LpDowncast(ref lp_base, variant_def_id) => {
452                 out.push('(');
453                 self.append_loan_path_to_string(&lp_base, out);
454                 out.push_str(DOWNCAST_PRINTED_OPERATOR);
455                 out.push_str(&self.tcx.def_path_str(variant_def_id));
456                 out.push(')');
457             }
458
459             LpExtend(ref lp_base, _, LpInterior(_, InteriorField(mc::FieldIndex(_, info)))) => {
460                 self.append_autoderefd_loan_path_to_string(&lp_base, out);
461                 out.push('.');
462                 out.push_str(&info.as_str());
463             }
464
465             LpExtend(ref lp_base, _, LpInterior(_, InteriorElement)) => {
466                 self.append_autoderefd_loan_path_to_string(&lp_base, out);
467                 out.push_str("[..]");
468             }
469
470             LpExtend(ref lp_base, _, LpDeref(_)) => {
471                 out.push('*');
472                 self.append_loan_path_to_string(&lp_base, out);
473             }
474         }
475     }
476
477     pub fn append_autoderefd_loan_path_to_string(&self,
478                                                  loan_path: &LoanPath<'tcx>,
479                                                  out: &mut String) {
480         match loan_path.kind {
481             LpExtend(ref lp_base, _, LpDeref(_)) => {
482                 // For a path like `(*x).f` or `(*x)[3]`, autoderef
483                 // rules would normally allow users to omit the `*x`.
484                 // So just serialize such paths to `x.f` or x[3]` respectively.
485                 self.append_autoderefd_loan_path_to_string(&lp_base, out)
486             }
487
488             LpDowncast(ref lp_base, variant_def_id) => {
489                 out.push('(');
490                 self.append_autoderefd_loan_path_to_string(&lp_base, out);
491                 out.push_str(DOWNCAST_PRINTED_OPERATOR);
492                 out.push_str(&self.tcx.def_path_str(variant_def_id));
493                 out.push(')');
494             }
495
496             LpVar(..) | LpUpvar(..) | LpExtend(.., LpInterior(..)) => {
497                 self.append_loan_path_to_string(loan_path, out)
498             }
499         }
500     }
501
502     pub fn loan_path_to_string(&self, loan_path: &LoanPath<'tcx>) -> String {
503         let mut result = String::new();
504         self.append_loan_path_to_string(loan_path, &mut result);
505         result
506     }
507
508     pub fn cmt_to_cow_str(&self, cmt: &mc::cmt_<'tcx>) -> Cow<'static, str> {
509         cmt.descriptive_string(self.tcx)
510     }
511
512     pub fn cmt_to_path_or_string(&self, cmt: &mc::cmt_<'tcx>) -> String {
513         match opt_loan_path(cmt) {
514             Some(lp) => format!("`{}`", self.loan_path_to_string(&lp)),
515             None => self.cmt_to_cow_str(cmt).into_owned(),
516         }
517     }
518 }
519
520 impl BitwiseOperator for LoanDataFlowOperator {
521     #[inline]
522     fn join(&self, succ: usize, pred: usize) -> usize {
523         succ | pred // loans from both preds are in scope
524     }
525 }
526
527 impl DataFlowOperator for LoanDataFlowOperator {
528     #[inline]
529     fn initial_value(&self) -> bool {
530         false // no loans in scope by default
531     }
532 }
533
534 impl fmt::Debug for InteriorKind {
535     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536         match *self {
537             InteriorField(mc::FieldIndex(_, info)) => write!(f, "{}", info),
538             InteriorElement => write!(f, "[]"),
539         }
540     }
541 }
542
543 impl<'tcx> fmt::Debug for Loan<'tcx> {
544     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545         write!(f, "Loan_{}({:?}, {:?}, {:?}-{:?}, {:?})",
546                self.index,
547                self.loan_path,
548                self.kind,
549                self.gen_scope,
550                self.kill_scope,
551                self.restricted_paths)
552     }
553 }
554
555 impl<'tcx> fmt::Debug for LoanPath<'tcx> {
556     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
557         match self.kind {
558             LpVar(id) => {
559                 write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().node_to_string(id)))
560             }
561
562             LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath {hir_id: var_id}, closure_expr_id }) => {
563                 let s = ty::tls::with(|tcx| {
564                     tcx.hir().node_to_string(var_id)
565                 });
566                 write!(f, "$({} captured by id={:?})", s, closure_expr_id)
567             }
568
569             LpDowncast(ref lp, variant_def_id) => {
570                 let variant_str = if variant_def_id.is_local() {
571                     ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
572                 } else {
573                     format!("{:?}", variant_def_id)
574                 };
575                 write!(f, "({:?}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
576             }
577
578             LpExtend(ref lp, _, LpDeref(_)) => {
579                 write!(f, "{:?}.*", lp)
580             }
581
582             LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
583                 write!(f, "{:?}.{:?}", lp, interior)
584             }
585         }
586     }
587 }
588
589 impl<'tcx> fmt::Display for LoanPath<'tcx> {
590     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591         match self.kind {
592             LpVar(id) => {
593                 write!(f, "$({})", ty::tls::with(|tcx| tcx.hir().hir_to_user_string(id)))
594             }
595
596             LpUpvar(ty::UpvarId{ var_path: ty::UpvarPath { hir_id }, closure_expr_id: _ }) => {
597                 let s = ty::tls::with(|tcx| {
598                     tcx.hir().node_to_string(hir_id)
599                 });
600                 write!(f, "$({} captured by closure)", s)
601             }
602
603             LpDowncast(ref lp, variant_def_id) => {
604                 let variant_str = if variant_def_id.is_local() {
605                     ty::tls::with(|tcx| tcx.def_path_str(variant_def_id))
606                 } else {
607                     format!("{:?}", variant_def_id)
608                 };
609                 write!(f, "({}{}{})", lp, DOWNCAST_PRINTED_OPERATOR, variant_str)
610             }
611
612             LpExtend(ref lp, _, LpDeref(_)) => {
613                 write!(f, "{}.*", lp)
614             }
615
616             LpExtend(ref lp, _, LpInterior(_, ref interior)) => {
617                 write!(f, "{}.{:?}", lp, interior)
618             }
619         }
620     }
621 }