diplomat_core/hir/
elision.rs

1//! This module provides the functionality for lowering lifetimes from the AST
2//! to the HIR, while simultaneously inferencing elided lifetimes.
3//!
4//! Full elision rules can be found in the [Nomicon].
5//!
6//! The key factor about lifetime elision is that all elision in the output of
7//! the method (if there is any) corresponds to exactly one lifetime in the method
8//! arguments, which may or may not be elided. Therefore, our task is to find this
9//! potential lifetime first, so that if we encounter an elided lifetime while
10//! lowering the output, we know which lifetime it corresponds to.
11//!
12//! # Unspoken Rules of Elision.
13//!
14//! Broadly speaking, the Nomicon defines the elision rules are such:
15//! 1. If there's a `&self` or `&mut self`, the lifetime of that borrow
16//!    corresponds to elision in the output.
17//! 2. Otherwise, if there's exactly one lifetime in the input, then that lifetime
18//!    corresponds to elision in the output.
19//! 3. If neither of these cases hold, then the output cannot contain elision.
20//!
21//! What the Nomicon doesn't tell you is that there are weird corner cases around
22//! using the `Self` type. Specifically, lifetimes in the `Self` type and in the
23//! type of the `self` argument (optional) aren't considered when figuring out
24//! which lifetime should correspond to elision in the output.
25//!
26//! Check out the following code:
27//! ```compile_fail
28//! struct Foo<'a>(&'a str);
29//!
30//! impl<'a> Foo<'a> {
31//!     fn get(self) -> &str { self.0 }
32//! }
33//! ```
34//! This code will fail to compile because it doesn't look at the `'a` in the
35//! `Foo<'a>`, which is what the type of `self` expands to. Therefore, it will
36//! conclude that there's nothing for the output to borrow from.
37//! This can be fixed by returning `&'a str` though. Many of the design
38//! decisions in this module were made to be able to replicate this behavior.
39//!
40//! You may be asking "why would we care about rejecting code that rustc rejects
41//! before it reaches us?" And the answer is this:
42//! ```rust
43//! # struct Foo<'a>(&'a str);
44//! impl<'a> Foo<'a> {
45//!     fn get(self, s: &str) -> &str { s }
46//! }
47//! ```
48//! This code is accepted by rustc, since it only considers the lifetime of `s`
49//! when searching for a lifetime that corresponds to output elision. If we were
50//! to naively look at all the lifetimes, we would see the lifetime in the `self`
51//! argument and the lifetime of `s`, making us reject this method. Therefore, we
52//! have to be extremely careful when traversing lifetimes, and make sure that
53//! lifetimes of `Self` are lowered but _not_ considered for elision, while other
54//! lifetimes are lowered while also being considered for elision.
55//!
56//! # Lowering and Inference
57//!
58//! Lowering and elision inference is broken into three distinct stages:
59//! 1. Lowering the borrow in `&self` or `&mut self`, if there is one.
60//! 2. Lowering lifetimes of other params.
61//! 3. Lowering lifetimes of the output.
62//!
63//! Although each stage fundementally lowers lifetimes, they behave differently
64//! when lowering elided lifetimes. Naturally, this module represents each stage
65//! as a state in a state machine.
66//!
67//! The first state is represented by the [`SelfParamLifetimeLowerer`] type.
68//! Since there is either zero or one occurrences of `&self` or `&mut self`, it
69//! exposes the `.no_self_ref()` and `.lower_self_ref(lt)` methods respectively,
70//! which consume the `SelfParamLifetimeLowerer` and return the next state,
71//! [`ParamLifetimeLowerer`], as well as the lowered lifetime. The reason these
72//! are two distinct types is that the lifetime in `&self` and `&mut self` takes
73//! precedence over any other lifetimes in the input, so `.lower_self_ref(lt)`
74//! tells the next state that the candidate lifetime is already found, and to
75//! generate fresh anonymous lifetimes for any elided lifetimes.
76//!
77//! The second state is represented by the [`ParamLifetimeLowerer`] type.
78//! It implements a helper trait, [`LifetimeLowerer`], which abstracts the lowering
79//! of references and generic lifetimes. Internally, it wraps an [`ElisionSource`],
80//! which acts as a state machine for tracking candidate lifetimes to correspond
81//! to elision in the output. When a lifetime that's not in the type of the `self`
82//! argument or in the expanded generics of the `Self` type is visited, this
83//! state machine is potentially updated to another state. If the lifetime is
84//! anonymous, it's added to the internal list of nodes that go into the final
85//! [`LifetimeEnv`] after lowering. Once all the lifetimes in the input are
86//! lowered, the `into_return_ltl()` method is called to transition into the
87//! final state.
88//!
89//! The third and final state is represented by the [`ReturnLifetimeLowerer`] type.
90//! Similar to `ParamLifetimeLowerer`, it also implements the [`LifetimeLowerer`]
91//! helper trait. However, it differs from `ParamLifetimeLowerer` since instead
92//! of potentially updating the internal `ElisionSource` when visiting a lifetime,
93//! it instead reads from it when an elided lifetime occurs. Once all the output
94//! lifetimes are lowered, `.finish()` is called to return the finalized
95//! [`LifetimeEnv`].
96//!
97//! [Nomicon]: https://doc.rust-lang.org/nomicon/lifetime-elision.html
98
99use super::lifetimes::{BoundedLifetime, Lifetime, LifetimeEnv, Lifetimes, MaybeStatic};
100use super::LoweringContext;
101use crate::ast;
102use smallvec::SmallVec;
103
104/// Lower [`ast::Lifetime`]s to [`Lifetime`]s.
105///
106/// This helper traits allows the [`lower_type`] and [`lower_out_type`] methods
107/// to abstractly lower lifetimes without concern for what sort of tracking
108/// goes on. In particular, elision inference requires updating internal state
109/// when visiting lifetimes in the input.
110pub trait LifetimeLowerer {
111    /// Lowers an [`ast::Lifetime`].
112    fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime>;
113
114    /// Lowers a slice of [`ast::Lifetime`]s by calling
115    /// [`LifetimeLowerer::lower_lifetime`] repeatedly.
116    ///
117    /// `type_generics` is the full list of generics on the type definition of the type
118    /// this lifetimes list is found on (needed for generating anon lifetimes)
119    fn lower_lifetimes(
120        &mut self,
121        lifetimes: &[ast::Lifetime],
122        type_generics: &ast::LifetimeEnv,
123    ) -> Lifetimes {
124        let mut lifetimes = Lifetimes::from_fn(lifetimes, |lifetime| self.lower_lifetime(lifetime));
125
126        for _ in lifetimes.as_slice().len()..type_generics.nodes.len() {
127            lifetimes.append_lifetime(self.lower_lifetime(&ast::Lifetime::Anonymous))
128        }
129        lifetimes
130    }
131
132    /// Lowers a slice of [`ast::Lifetime`], where the strategy may vary depending
133    /// on whether or not the lifetimes are expanded from the `Self` type.
134    ///
135    /// The distinction between this and [`LifetimeLowerer::lower_lifetimes`] is
136    /// that if `Self` expands to a type with anonymous lifetimes like `Foo<'_>`,
137    /// then multiple instances of `Self` should expand to have the same anonymous
138    /// lifetime, and this lifetime can be cached inside of the `self` argument.
139    /// Additionally, elision inferences knows to not search inside the generics
140    /// of `Self` types for candidate lifetimes to correspond to elided lifetimes
141    /// in the output.
142    ///
143    /// `type_generics` is the full list of generics on the type definition of the type
144    /// this generics list is found on (needed for generating anon lifetimes)
145    fn lower_generics(
146        &mut self,
147        lifetimes: &[ast::Lifetime],
148        type_generics: &ast::LifetimeEnv,
149        is_self: bool,
150    ) -> Lifetimes;
151}
152
153/// A state machine for tracking which lifetime in a function's parameters
154/// may correspond to elided lifetimes in the output.
155#[derive(Copy, Clone)]
156enum ElisionSource {
157    /// No borrows in the input, no elision.
158    NoBorrows,
159    /// `&self` or `&mut self`, elision allowed.
160    SelfParam(MaybeStatic<Lifetime>),
161    /// One param contains a borrow, elision allowed.
162    OneParam(MaybeStatic<Lifetime>),
163    /// Multiple borrows and no self borrow, no elision.
164    MultipleBorrows,
165}
166
167impl ElisionSource {
168    /// Potentially transition to a new state.
169    fn visit_lifetime(&mut self, lifetime: MaybeStatic<Lifetime>) {
170        match self {
171            ElisionSource::NoBorrows => *self = ElisionSource::OneParam(lifetime),
172            ElisionSource::SelfParam(_) => {
173                // References to self have the highest precedence, do nothing.
174            }
175            ElisionSource::OneParam(_) => *self = ElisionSource::MultipleBorrows,
176            ElisionSource::MultipleBorrows => {
177                // There's ambiguity. This is valid when there's no elision in
178                // the output.
179            }
180        };
181    }
182}
183
184/// A type for storing shared information between the different states of elision
185/// inference.
186///
187/// This contains data for generating fresh elided lifetimes, looking up named
188/// lifetimes, and caching lifetimes of `Self`.
189pub(super) struct BaseLifetimeLowerer<'ast> {
190    lifetime_env: &'ast ast::LifetimeEnv,
191    self_lifetimes: Option<Lifetimes>,
192    nodes: SmallVec<[BoundedLifetime; super::lifetimes::INLINE_NUM_LIFETIMES]>,
193    num_lifetimes: usize,
194}
195
196/// The first phase of output elision inference.
197///
198/// In the first phase, the type signature of the `&self` or `&mut self` type
199/// is lowered into its HIR representation, if present. According to elision
200/// rules, this reference has the highest precedence as the lifetime that
201/// goes into elision in the output, and so it's checked first.
202pub(super) struct SelfParamLifetimeLowerer<'ast> {
203    base: BaseLifetimeLowerer<'ast>,
204}
205
206/// The second phase of output elision inference.
207///
208/// In the second phase, all lifetimes in the parameter type signatures
209/// (besides the lifetime of self, if present) are lowered. If a self param
210/// didn't claim the potential output elided lifetime, then if there's a
211/// single lifetime (elided or not) in the inputs, it will claim the
212/// potential output elided lifetime.
213pub(super) struct ParamLifetimeLowerer<'ast> {
214    elision_source: ElisionSource,
215    base: BaseLifetimeLowerer<'ast>,
216}
217
218/// The third and final phase of output elision inference.
219///
220/// In the third phase, the type signature of the output type is lowered into
221/// its HIR representation. If one of the input lifetimes were marked as
222/// responsible for any elision in the output, then anonymous lifetimes get
223/// that lifetime. If none did and there is elision in the output, then
224/// rustc should have errored and said the elision was ambiguous, meaning
225/// that state should be impossible so it panics.
226pub(super) struct ReturnLifetimeLowerer<'ast> {
227    elision_source: ElisionSource,
228    base: BaseLifetimeLowerer<'ast>,
229}
230
231impl<'ast> BaseLifetimeLowerer<'ast> {
232    /// Returns a [`Lifetime`] representing a new anonymous lifetime, and
233    /// pushes it to the nodes vector.
234    fn new_elided(&mut self) -> Lifetime {
235        let index = self.num_lifetimes;
236        self.num_lifetimes += 1;
237        Lifetime::new(index)
238    }
239
240    /// Lowers a single [`ast::Lifetime`]. If the lifetime is elided, then a fresh
241    /// [`ImplicitLifetime`] is generated.
242    fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
243        match lifetime {
244            ast::Lifetime::Static => MaybeStatic::Static,
245            ast::Lifetime::Named(named) => {
246                MaybeStatic::NonStatic(Lifetime::from_ast(named, self.lifetime_env))
247            }
248            ast::Lifetime::Anonymous => MaybeStatic::NonStatic(self.new_elided()),
249        }
250    }
251
252    /// Retrieves the cached  `Self` lifetimes, or caches newly generated
253    /// lifetimes and returns those.
254    fn self_lifetimes_or_new(&mut self, ast_lifetimes: &[ast::Lifetime]) -> Lifetimes {
255        if let Some(lifetimes) = &self.self_lifetimes {
256            lifetimes.clone()
257        } else {
258            let lifetimes = Lifetimes::from_fn(ast_lifetimes, |lt| self.lower_lifetime(lt));
259            self.self_lifetimes = Some(lifetimes.clone());
260            lifetimes
261        }
262    }
263}
264
265impl<'ast> SelfParamLifetimeLowerer<'ast> {
266    /// Returns a new [`SelfParamLifetimeLowerer`].
267    pub fn new(
268        lifetime_env: &'ast ast::LifetimeEnv,
269        ctx: &mut LoweringContext,
270    ) -> Result<Self, ()> {
271        let mut hir_nodes = Ok(SmallVec::new());
272
273        for ast_node in lifetime_env.nodes.iter() {
274            let lifetime = ctx.lower_ident(ast_node.lifetime.name(), "named lifetime");
275            match (lifetime, &mut hir_nodes) {
276                (Ok(lifetime), Ok(hir_nodes)) => {
277                    hir_nodes.push(BoundedLifetime::new(
278                        lifetime,
279                        ast_node.longer.iter().map(|i| Lifetime::new(*i)).collect(),
280                        ast_node.shorter.iter().map(|i| Lifetime::new(*i)).collect(),
281                    ));
282                }
283                _ => hir_nodes = Err(()),
284            }
285        }
286
287        hir_nodes.map(|nodes| Self {
288            base: BaseLifetimeLowerer {
289                lifetime_env,
290                self_lifetimes: None,
291                num_lifetimes: nodes.len(),
292                nodes,
293            },
294        })
295    }
296
297    /// Lowers the lifetime of `&self` or `&mut self`.
298    ///
299    /// The lifetimes of `&self` and `&mut self` are special, because they
300    /// automatically take priority over any other lifetime in the input for
301    /// being tied to any elided lifetimes in the output.
302    ///
303    /// Along with returning the lowered lifetime, this method also returns the
304    /// next state in elision inference, the [`ParamLifetimeLowerer`].
305    pub fn lower_self_ref(
306        mut self,
307        lifetime: &ast::Lifetime,
308    ) -> (MaybeStatic<Lifetime>, ParamLifetimeLowerer<'ast>) {
309        let self_lifetime = self.base.lower_lifetime(lifetime);
310
311        (
312            self_lifetime,
313            self.into_param_ltl(ElisionSource::SelfParam(self_lifetime)),
314        )
315    }
316
317    /// Acknowledges that there's no `&self` or `&mut self`, and transitions
318    /// to the next state, [`ParamLifetimeLowerer`].
319    pub fn no_self_ref(self) -> ParamLifetimeLowerer<'ast> {
320        self.into_param_ltl(ElisionSource::NoBorrows)
321    }
322
323    /// Transition into the next state, [`ParamLifetimeLowerer`].
324    fn into_param_ltl(self, elision_source: ElisionSource) -> ParamLifetimeLowerer<'ast> {
325        ParamLifetimeLowerer {
326            elision_source,
327            base: self.base,
328        }
329    }
330}
331
332impl<'ast> ParamLifetimeLowerer<'ast> {
333    /// Once all lifetimes in the parameters are lowered, this function is
334    /// called to transition to the next state, [`ReturnLifetimeLowerer`].
335    pub fn into_return_ltl(self) -> ReturnLifetimeLowerer<'ast> {
336        ReturnLifetimeLowerer {
337            elision_source: self.elision_source,
338            base: self.base,
339        }
340    }
341}
342
343impl<'ast> LifetimeLowerer for ParamLifetimeLowerer<'ast> {
344    fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
345        let lifetime = self.base.lower_lifetime(borrow);
346        self.elision_source.visit_lifetime(lifetime);
347        lifetime
348    }
349
350    fn lower_generics(
351        &mut self,
352        lifetimes: &[ast::Lifetime],
353        type_generics: &ast::LifetimeEnv,
354        is_self: bool,
355    ) -> Lifetimes {
356        if is_self {
357            self.base.self_lifetimes_or_new(lifetimes)
358        } else {
359            self.lower_lifetimes(lifetimes, type_generics)
360        }
361    }
362}
363
364impl<'ast> ReturnLifetimeLowerer<'ast> {
365    /// Finalize the lifetimes in the method, returning the resulting [`LifetimeEnv`].
366    pub fn finish(self) -> LifetimeEnv {
367        LifetimeEnv::new(self.base.nodes, self.base.num_lifetimes)
368    }
369}
370
371impl<'ast> LifetimeLowerer for ReturnLifetimeLowerer<'ast> {
372    fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
373        match borrow {
374            ast::Lifetime::Static => MaybeStatic::Static,
375            ast::Lifetime::Named(named) => {
376                MaybeStatic::NonStatic(Lifetime::from_ast(named, self.base.lifetime_env))
377            }
378            ast::Lifetime::Anonymous => match self.elision_source {
379                ElisionSource::SelfParam(lifetime) | ElisionSource::OneParam(lifetime) => lifetime,
380                ElisionSource::NoBorrows => {
381                    panic!("nothing to borrow from, this shouldn't pass rustc's checks")
382                }
383                ElisionSource::MultipleBorrows => {
384                    panic!("source of elision is ambiguous, this shouldn't pass rustc's checks")
385                }
386            },
387        }
388    }
389
390    fn lower_generics(
391        &mut self,
392        lifetimes: &[ast::Lifetime],
393        type_generics: &ast::LifetimeEnv,
394        is_self: bool,
395    ) -> Lifetimes {
396        if is_self {
397            self.base.self_lifetimes_or_new(lifetimes)
398        } else {
399            self.lower_lifetimes(lifetimes, type_generics)
400        }
401    }
402}
403
404impl LifetimeLowerer for &ast::LifetimeEnv {
405    fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
406        match lifetime {
407            ast::Lifetime::Static => MaybeStatic::Static,
408            ast::Lifetime::Named(named) => MaybeStatic::NonStatic(Lifetime::from_ast(named, self)),
409            ast::Lifetime::Anonymous => {
410                panic!("anonymous lifetime inside struct, this shouldn't pass rustc's checks")
411            }
412        }
413    }
414
415    fn lower_generics(
416        &mut self,
417        lifetimes: &[ast::Lifetime],
418        type_generics: &ast::LifetimeEnv,
419        _: bool,
420    ) -> Lifetimes {
421        self.lower_lifetimes(lifetimes, type_generics)
422    }
423}
424
425// Things to test:
426// 1. ensure that if there are multiple inputs that are `Self`, where `Self` has
427//    an elided lifetime, all expansions of `Self` have the same anonymous lifetimes.
428
429#[cfg(test)]
430mod tests {
431    use strck::IntoCk;
432
433    /// Convert a syntax tree into a [`TypeContext`].
434    macro_rules! tcx {
435        ($($tokens:tt)*) => {{
436            let m = crate::ast::Module::from_syn(&syn::parse_quote! { $($tokens)* }, true);
437
438            let mut env = crate::Env::default();
439            let mut top_symbols = crate::ModuleEnv::new(Default::default());
440
441            m.insert_all_types(crate::ast::Path::empty(), &mut env);
442            top_symbols.insert(m.name.clone(), crate::ast::ModSymbol::SubModule(m.name.clone()));
443
444            env.insert(crate::ast::Path::empty(), top_symbols);
445
446            let mut backend = crate::hir::BasicAttributeValidator::new("test-backend");
447            backend.support.static_slices = true;
448
449            // Don't run validation: it will error on elision. We want this code to support
450            // elision even if we don't actually allow it, since good diagnostics involve understanding
451            // broken code.
452            let (_, tcx) = crate::hir::TypeContext::from_ast_without_validation(&env, Default::default(), backend).unwrap();
453
454            tcx
455        }}
456    }
457
458    macro_rules! do_test {
459        ($($tokens:tt)*) => {{
460            let mut settings = insta::Settings::new();
461            settings.set_sort_maps(true);
462
463            settings.bind(|| {
464                let tcx = tcx! { $($tokens)* };
465
466                insta::assert_debug_snapshot!(tcx);
467            })
468        }}
469    }
470
471    #[test]
472    fn simple_mod() {
473        do_test! {
474            mod ffi {
475                #[diplomat::opaque]
476                struct Opaque<'a> {
477                    s: DiplomatStrSlice<'a>,
478                }
479
480                struct Struct<'a> {
481                    s:  DiplomatStrSlice<'a>,
482                }
483
484                #[diplomat::out]
485                struct OutStruct<'a> {
486                    inner: Box<Opaque<'a>>,
487                }
488
489                impl<'a> OutStruct<'a> {
490                    pub fn new(s: &'a DiplomatStr) -> Self {
491                        Self { inner: Box::new(Opaque { s }) }
492                    }
493
494                }
495
496                impl<'a> Struct<'a> {
497                    pub fn rustc_elision(self, s: &DiplomatStr) -> &DiplomatStr {
498                        s
499                    }
500                }
501            }
502        }
503    }
504
505    #[test]
506    fn test_elision_in_struct() {
507        let tcx = tcx! {
508            mod ffi {
509                #[diplomat::opaque]
510                struct Opaque;
511
512                #[diplomat::opaque]
513                struct Opaque2<'a>(&'a str);
514
515                impl Opaque {
516                    // This should have two elided lifetimes
517                    pub fn elided(&self, x: &Opaque2) {
518
519                    }
520                }
521            }
522        };
523
524        let method = &tcx
525            .opaques()
526            .iter()
527            .find(|def| def.name == "Opaque")
528            .unwrap()
529            .methods[0];
530
531        assert_eq!(
532            method.lifetime_env.num_lifetimes(),
533            3,
534            "elided() must have three anon lifetimes"
535        );
536        insta::assert_debug_snapshot!(method);
537    }
538
539    #[test]
540    fn test_borrowing_fields() {
541        use std::collections::BTreeMap;
542        use std::fmt;
543
544        let tcx = tcx! {
545            mod ffi {
546                #[diplomat::opaque]
547                pub struct Opaque;
548
549                struct Input<'p, 'q> {
550                    p_data: &'p Opaque,
551                    q_data: &'q Opaque,
552                    name: DiplomatStrSlice<'static>,
553                    inner: Inner<'q>,
554                }
555
556                struct Inner<'a> {
557                    more_data: DiplomatStrSlice<'a>,
558                }
559
560                struct Output<'p,'q> {
561                    p_data: &'p Opaque,
562                    q_data: &'q Opaque,
563                }
564
565                impl<'a, 'b> Input<'a, 'b> {
566                    pub fn as_output(self, _s: &'static DiplomatStr) -> Output<'b, 'a> {
567                        Output { data: self.data }
568                    }
569
570                }
571            }
572        };
573
574        let method = &tcx
575            .structs()
576            .iter()
577            .find(|def| def.name == "Input")
578            .unwrap()
579            .methods[0];
580
581        let visitor = method.borrowing_field_visitor(&tcx, "this".ck().unwrap());
582        let mut lt_to_borrowing_fields: BTreeMap<_, Vec<_>> = BTreeMap::new();
583        visitor.visit_borrowing_fields(|lt, bf| {
584            lt_to_borrowing_fields
585                .entry(lt)
586                .or_default()
587                .push(DebugBorrowingField(bf));
588        });
589
590        struct DebugBorrowingField<'m>(crate::hir::borrowing_field::BorrowingField<'m>);
591
592        impl<'m> fmt::Debug for DebugBorrowingField<'m> {
593            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
594                f.write_str("\"")?;
595                self.0.try_backtrace(|i, ident| {
596                    if i != 0 {
597                        f.write_str(".")?;
598                    }
599                    f.write_str(ident.as_str())
600                })?;
601                f.write_str("\"")
602            }
603        }
604
605        let mut settings = insta::Settings::new();
606        settings.set_sort_maps(true);
607
608        settings.bind(|| {
609            insta::assert_debug_snapshot!(lt_to_borrowing_fields);
610        })
611    }
612}