use std::collections::{BTreeMap, BTreeSet};
use crate::hir::{self, Method, StructDef, StructPath, TyPosition, TypeContext};
use crate::hir::lifetimes::{Lifetime, LifetimeEnv, MaybeStatic};
use crate::hir::ty_position::StructPathLike;
pub struct BorrowingParamVisitor<'tcx> {
tcx: &'tcx TypeContext,
used_method_lifetimes: BTreeSet<Lifetime>,
borrow_map: BTreeMap<Lifetime, BorrowedLifetimeInfo<'tcx>>,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct LifetimeEdge<'tcx> {
pub param_name: String,
pub kind: LifetimeEdgeKind<'tcx>,
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug)]
pub enum LifetimeEdgeKind<'tcx> {
OpaqueParam,
SliceParam,
StructLifetime(&'tcx LifetimeEnv, Lifetime, bool),
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct BorrowedLifetimeInfo<'tcx> {
pub incoming_edges: Vec<LifetimeEdge<'tcx>>,
pub all_longer_lifetimes: BTreeSet<Lifetime>,
}
impl<'tcx> BorrowingParamVisitor<'tcx> {
pub(crate) fn new(
method: &'tcx Method,
tcx: &'tcx TypeContext,
force_include_slices: bool,
) -> Self {
let mut used_method_lifetimes = method.output.used_method_lifetimes();
if force_include_slices {
if let Some(s) = &method.param_self {
if let hir::SelfType::Struct(s) = &s.ty {
let st = s.resolve(tcx);
for f in &st.fields {
BorrowingParamVisitor::add_slices_to_used_lifetimes(
&mut used_method_lifetimes,
method,
tcx,
&f.ty,
);
}
}
}
for p in &method.params {
BorrowingParamVisitor::add_slices_to_used_lifetimes(
&mut used_method_lifetimes,
method,
tcx,
&p.ty,
);
}
}
let borrow_map = used_method_lifetimes
.iter()
.map(|lt| {
(
*lt,
BorrowedLifetimeInfo {
incoming_edges: Vec::new(),
all_longer_lifetimes: method
.lifetime_env
.all_longer_lifetimes(lt)
.collect(),
},
)
})
.collect();
BorrowingParamVisitor {
tcx,
used_method_lifetimes,
borrow_map,
}
}
pub fn all_longer_lifetimes(&self, lt: Lifetime) -> Option<&BTreeSet<Lifetime>> {
self.borrow_map.get(<).map(|x| &x.all_longer_lifetimes)
}
fn add_slices_to_used_lifetimes<P: TyPosition<StructPath = StructPath>>(
set: &mut BTreeSet<Lifetime>,
method: &'tcx Method,
tcx: &'tcx TypeContext,
ty: &hir::Type<P>,
) {
match ty {
hir::Type::Struct(s) => {
let st = s.resolve(tcx);
for f in &st.fields {
BorrowingParamVisitor::add_slices_to_used_lifetimes(set, method, tcx, &f.ty);
}
}
hir::Type::Slice(s) => {
if let Some(MaybeStatic::NonStatic(lt)) = s.lifetime() {
if method.lifetime_env.get_bounds(*lt).is_some() {
set.insert(*lt);
}
}
}
_ => {}
}
}
pub fn borrow_map(self) -> BTreeMap<Lifetime, BorrowedLifetimeInfo<'tcx>> {
self.borrow_map
}
pub fn visit_param<P: TyPosition<StructPath = StructPath>>(
&mut self,
ty: &hir::Type<P>,
param_name: &str,
) -> ParamBorrowInfo<'tcx> {
let mut is_borrowed = false;
if self.used_method_lifetimes.is_empty() {
if let hir::Type::Slice(..) = *ty {
return ParamBorrowInfo::TemporarySlice;
} else {
return ParamBorrowInfo::NotBorrowed;
}
}
if let hir::Type::Struct(s) = ty {
let mut borrowed_struct_lifetime_map = BTreeMap::<Lifetime, BTreeSet<Lifetime>>::new();
let link = s.link_lifetimes(self.tcx);
for (method_lifetime, method_lifetime_info) in &mut self.borrow_map {
for (use_lt, def_lt) in link.lifetimes_def_only() {
if let MaybeStatic::NonStatic(use_lt) = use_lt {
if method_lifetime_info.all_longer_lifetimes.contains(&use_lt) {
let edge = LifetimeEdge {
param_name: param_name.into(),
kind: LifetimeEdgeKind::StructLifetime(
link.def_env(),
def_lt,
ty.is_option(),
),
};
method_lifetime_info.incoming_edges.push(edge);
is_borrowed = true;
borrowed_struct_lifetime_map
.entry(def_lt)
.or_default()
.insert(*method_lifetime);
}
}
}
}
if is_borrowed {
ParamBorrowInfo::Struct(StructBorrowInfo {
env: link.def_env(),
borrowed_struct_lifetime_map,
})
} else {
ParamBorrowInfo::NotBorrowed
}
} else {
for method_lifetime in self.borrow_map.values_mut() {
for lt in ty.lifetimes() {
if let MaybeStatic::NonStatic(lt) = lt {
if method_lifetime.all_longer_lifetimes.contains(<) {
let kind = match ty {
hir::Type::Slice(..) => LifetimeEdgeKind::SliceParam,
hir::Type::Opaque(..) => LifetimeEdgeKind::OpaqueParam,
_ => unreachable!("Types other than slices, opaques, and structs cannot have lifetimes")
};
let edge = LifetimeEdge {
param_name: param_name.into(),
kind,
};
method_lifetime.incoming_edges.push(edge);
is_borrowed = true;
break;
}
}
}
}
match (is_borrowed, ty) {
(true, &hir::Type::Slice(..)) => ParamBorrowInfo::BorrowedSlice,
(false, &hir::Type::Slice(..)) => ParamBorrowInfo::TemporarySlice,
(false, _) => ParamBorrowInfo::NotBorrowed,
(true, _) => ParamBorrowInfo::BorrowedOpaque,
}
}
}
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum ParamBorrowInfo<'tcx> {
NotBorrowed,
TemporarySlice,
BorrowedSlice,
Struct(StructBorrowInfo<'tcx>),
BorrowedOpaque,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct StructBorrowInfo<'tcx> {
pub env: &'tcx LifetimeEnv,
pub borrowed_struct_lifetime_map: BTreeMap<Lifetime, BTreeSet<Lifetime>>,
}
impl<'tcx> StructBorrowInfo<'tcx> {
pub fn compute_for_struct_field<P: TyPosition>(
struc: &StructDef<P>,
field: &P::StructPath,
tcx: &'tcx TypeContext,
) -> Option<Self> {
if field.lifetimes().as_slice().is_empty() {
return None;
}
let mut borrowed_struct_lifetime_map = BTreeMap::<Lifetime, BTreeSet<Lifetime>>::new();
let link = field.link_lifetimes(tcx);
for outer_lt in struc.lifetimes.all_lifetimes() {
for (use_lt, def_lt) in link.lifetimes_def_only() {
if let MaybeStatic::NonStatic(use_lt) = use_lt {
if outer_lt == use_lt {
borrowed_struct_lifetime_map
.entry(def_lt)
.or_default()
.insert(outer_lt);
}
}
}
}
if borrowed_struct_lifetime_map.is_empty() {
None
} else {
Some(StructBorrowInfo {
env: link.def_env(),
borrowed_struct_lifetime_map,
})
}
}
}