use super::lifetimes::{BoundedLifetime, Lifetime, LifetimeEnv, Lifetimes, MaybeStatic};
use super::LoweringContext;
use crate::ast;
use crate::hir::ty_position::Sealed;
use smallvec::SmallVec;
pub trait LifetimeLowerer: Sealed {
fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime>;
fn lower_lifetimes(
&mut self,
lifetimes: &[ast::Lifetime],
type_generics: &ast::LifetimeEnv,
) -> Lifetimes {
let mut lifetimes = Lifetimes::from_fn(lifetimes, |lifetime| self.lower_lifetime(lifetime));
for _ in lifetimes.as_slice().len()..type_generics.nodes.len() {
lifetimes.append_lifetime(self.lower_lifetime(&ast::Lifetime::Anonymous))
}
lifetimes
}
fn lower_generics(
&mut self,
lifetimes: &[ast::Lifetime],
type_generics: &ast::LifetimeEnv,
is_self: bool,
) -> Lifetimes;
}
#[derive(Copy, Clone)]
enum ElisionSource {
NoBorrows,
SelfParam(MaybeStatic<Lifetime>),
OneParam(MaybeStatic<Lifetime>),
MultipleBorrows,
}
impl ElisionSource {
fn visit_lifetime(&mut self, lifetime: MaybeStatic<Lifetime>) {
match self {
ElisionSource::NoBorrows => *self = ElisionSource::OneParam(lifetime),
ElisionSource::SelfParam(_) => {
}
ElisionSource::OneParam(_) => *self = ElisionSource::MultipleBorrows,
ElisionSource::MultipleBorrows => {
}
};
}
}
pub(super) struct BaseLifetimeLowerer<'ast> {
lifetime_env: &'ast ast::LifetimeEnv,
self_lifetimes: Option<Lifetimes>,
nodes: SmallVec<[BoundedLifetime; super::lifetimes::INLINE_NUM_LIFETIMES]>,
num_lifetimes: usize,
}
pub(super) struct SelfParamLifetimeLowerer<'ast> {
base: BaseLifetimeLowerer<'ast>,
}
pub(super) struct ParamLifetimeLowerer<'ast> {
elision_source: ElisionSource,
base: BaseLifetimeLowerer<'ast>,
}
pub(super) struct ReturnLifetimeLowerer<'ast> {
elision_source: ElisionSource,
base: BaseLifetimeLowerer<'ast>,
}
impl<'ast> Sealed for BaseLifetimeLowerer<'ast> {}
impl<'ast> Sealed for SelfParamLifetimeLowerer<'ast> {}
impl<'ast> Sealed for ParamLifetimeLowerer<'ast> {}
impl<'ast> Sealed for ReturnLifetimeLowerer<'ast> {}
impl<'ast> Sealed for &'ast ast::LifetimeEnv {}
impl<'ast> BaseLifetimeLowerer<'ast> {
fn new_elided(&mut self) -> Lifetime {
let index = self.num_lifetimes;
self.num_lifetimes += 1;
Lifetime::new(index)
}
fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
match lifetime {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => {
MaybeStatic::NonStatic(Lifetime::from_ast(named, self.lifetime_env))
}
ast::Lifetime::Anonymous => MaybeStatic::NonStatic(self.new_elided()),
}
}
fn self_lifetimes_or_new(&mut self, ast_lifetimes: &[ast::Lifetime]) -> Lifetimes {
if let Some(lifetimes) = &self.self_lifetimes {
lifetimes.clone()
} else {
let lifetimes = Lifetimes::from_fn(ast_lifetimes, |lt| self.lower_lifetime(lt));
self.self_lifetimes = Some(lifetimes.clone());
lifetimes
}
}
}
impl<'ast> SelfParamLifetimeLowerer<'ast> {
pub fn new(
lifetime_env: &'ast ast::LifetimeEnv,
ctx: &mut LoweringContext,
) -> Result<Self, ()> {
let mut hir_nodes = Ok(SmallVec::new());
for ast_node in lifetime_env.nodes.iter() {
let lifetime = ctx.lower_ident(ast_node.lifetime.name(), "named lifetime");
match (lifetime, &mut hir_nodes) {
(Ok(lifetime), Ok(hir_nodes)) => {
hir_nodes.push(BoundedLifetime::new(
lifetime,
ast_node.longer.iter().map(|i| Lifetime::new(*i)).collect(),
ast_node.shorter.iter().map(|i| Lifetime::new(*i)).collect(),
));
}
_ => hir_nodes = Err(()),
}
}
hir_nodes.map(|nodes| Self {
base: BaseLifetimeLowerer {
lifetime_env,
self_lifetimes: None,
num_lifetimes: nodes.len(),
nodes,
},
})
}
pub fn lower_self_ref(
mut self,
lifetime: &ast::Lifetime,
) -> (MaybeStatic<Lifetime>, ParamLifetimeLowerer<'ast>) {
let self_lifetime = self.base.lower_lifetime(lifetime);
(
self_lifetime,
self.into_param_ltl(ElisionSource::SelfParam(self_lifetime)),
)
}
pub fn no_self_ref(self) -> ParamLifetimeLowerer<'ast> {
self.into_param_ltl(ElisionSource::NoBorrows)
}
fn into_param_ltl(self, elision_source: ElisionSource) -> ParamLifetimeLowerer<'ast> {
ParamLifetimeLowerer {
elision_source,
base: self.base,
}
}
}
impl<'ast> ParamLifetimeLowerer<'ast> {
pub fn into_return_ltl(self) -> ReturnLifetimeLowerer<'ast> {
ReturnLifetimeLowerer {
elision_source: self.elision_source,
base: self.base,
}
}
}
impl<'ast> LifetimeLowerer for ParamLifetimeLowerer<'ast> {
fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
let lifetime = self.base.lower_lifetime(borrow);
self.elision_source.visit_lifetime(lifetime);
lifetime
}
fn lower_generics(
&mut self,
lifetimes: &[ast::Lifetime],
type_generics: &ast::LifetimeEnv,
is_self: bool,
) -> Lifetimes {
if is_self {
self.base.self_lifetimes_or_new(lifetimes)
} else {
self.lower_lifetimes(lifetimes, type_generics)
}
}
}
impl<'ast> ReturnLifetimeLowerer<'ast> {
pub fn finish(self) -> LifetimeEnv {
LifetimeEnv::new(self.base.nodes, self.base.num_lifetimes)
}
}
impl<'ast> LifetimeLowerer for ReturnLifetimeLowerer<'ast> {
fn lower_lifetime(&mut self, borrow: &ast::Lifetime) -> MaybeStatic<Lifetime> {
match borrow {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => {
MaybeStatic::NonStatic(Lifetime::from_ast(named, self.base.lifetime_env))
}
ast::Lifetime::Anonymous => match self.elision_source {
ElisionSource::SelfParam(lifetime) | ElisionSource::OneParam(lifetime) => lifetime,
ElisionSource::NoBorrows => {
panic!("nothing to borrow from, this shouldn't pass rustc's checks")
}
ElisionSource::MultipleBorrows => {
panic!("source of elision is ambiguous, this shouldn't pass rustc's checks")
}
},
}
}
fn lower_generics(
&mut self,
lifetimes: &[ast::Lifetime],
type_generics: &ast::LifetimeEnv,
is_self: bool,
) -> Lifetimes {
if is_self {
self.base.self_lifetimes_or_new(lifetimes)
} else {
self.lower_lifetimes(lifetimes, type_generics)
}
}
}
impl LifetimeLowerer for &ast::LifetimeEnv {
fn lower_lifetime(&mut self, lifetime: &ast::Lifetime) -> MaybeStatic<Lifetime> {
match lifetime {
ast::Lifetime::Static => MaybeStatic::Static,
ast::Lifetime::Named(named) => MaybeStatic::NonStatic(Lifetime::from_ast(named, self)),
ast::Lifetime::Anonymous => {
panic!("anonymous lifetime inside struct, this shouldn't pass rustc's checks")
}
}
}
fn lower_generics(
&mut self,
lifetimes: &[ast::Lifetime],
type_generics: &ast::LifetimeEnv,
_: bool,
) -> Lifetimes {
self.lower_lifetimes(lifetimes, type_generics)
}
}
#[cfg(test)]
mod tests {
use strck::IntoCk;
macro_rules! tcx {
($($tokens:tt)*) => {{
let m = crate::ast::Module::from_syn(&syn::parse_quote! { $($tokens)* }, true);
let mut env = crate::Env::default();
let mut top_symbols = crate::ModuleEnv::new(Default::default());
m.insert_all_types(crate::ast::Path::empty(), &mut env);
top_symbols.insert(m.name.clone(), crate::ast::ModSymbol::SubModule(m.name.clone()));
env.insert(crate::ast::Path::empty(), top_symbols);
let mut backend = crate::hir::BasicAttributeValidator::new("test-backend");
backend.support.static_slices = true;
let (_, tcx) = crate::hir::TypeContext::from_ast_without_validation(&env, Default::default(), backend).unwrap();
tcx
}}
}
macro_rules! do_test {
($($tokens:tt)*) => {{
let mut settings = insta::Settings::new();
settings.set_sort_maps(true);
settings.bind(|| {
let tcx = tcx! { $($tokens)* };
insta::assert_debug_snapshot!(tcx);
})
}}
}
#[test]
fn simple_mod() {
do_test! {
mod ffi {
#[diplomat::opaque]
struct Opaque<'a> {
s: DiplomatStrSlice<'a>,
}
struct Struct<'a> {
s: DiplomatStrSlice<'a>,
}
#[diplomat::out]
struct OutStruct<'a> {
inner: Box<Opaque<'a>>,
}
impl<'a> OutStruct<'a> {
pub fn new(s: &'a DiplomatStr) -> Self {
Self { inner: Box::new(Opaque { s }) }
}
}
impl<'a> Struct<'a> {
pub fn rustc_elision(self, s: &DiplomatStr) -> &DiplomatStr {
s
}
}
}
}
}
#[test]
fn test_elision_in_struct() {
let tcx = tcx! {
mod ffi {
#[diplomat::opaque]
struct Opaque;
#[diplomat::opaque]
struct Opaque2<'a>(&'a str);
impl Opaque {
pub fn elided(&self, x: &Opaque2) {
}
}
}
};
let method = &tcx
.opaques()
.iter()
.find(|def| def.name == "Opaque")
.unwrap()
.methods[0];
assert_eq!(
method.lifetime_env.num_lifetimes(),
3,
"elided() must have three anon lifetimes"
);
insta::assert_debug_snapshot!(method);
}
#[test]
fn test_borrowing_fields() {
use std::collections::BTreeMap;
use std::fmt;
let tcx = tcx! {
mod ffi {
#[diplomat::opaque]
pub struct Opaque;
struct Input<'p, 'q> {
p_data: &'p Opaque,
q_data: &'q Opaque,
name: DiplomatStrSlice<'static>,
inner: Inner<'q>,
}
struct Inner<'a> {
more_data: DiplomatStrSlice<'a>,
}
struct Output<'p,'q> {
p_data: &'p Opaque,
q_data: &'q Opaque,
}
impl<'a, 'b> Input<'a, 'b> {
pub fn as_output(self, _s: &'static DiplomatStr) -> Output<'b, 'a> {
Output { data: self.data }
}
}
}
};
let method = &tcx
.structs()
.iter()
.find(|def| def.name == "Input")
.unwrap()
.methods[0];
let visitor = method.borrowing_field_visitor(&tcx, "this".ck().unwrap());
let mut lt_to_borrowing_fields: BTreeMap<_, Vec<_>> = BTreeMap::new();
visitor.visit_borrowing_fields(|lt, bf| {
lt_to_borrowing_fields
.entry(lt)
.or_default()
.push(DebugBorrowingField(bf));
});
struct DebugBorrowingField<'m>(crate::hir::borrowing_field::BorrowingField<'m>);
impl<'m> fmt::Debug for DebugBorrowingField<'m> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("\"")?;
self.0.try_backtrace(|i, ident| {
if i != 0 {
f.write_str(".")?;
}
f.write_str(ident.as_str())
})?;
f.write_str("\"")
}
}
let mut settings = insta::Settings::new();
settings.set_sort_maps(true);
settings.bind(|| {
insta::assert_debug_snapshot!(lt_to_borrowing_fields);
})
}
}