use std::{collections::HashSet, mem};
use as_derive_utils::{spanned_err, syn_err};
use core_extensions::SelfOps;
use syn::{
spanned::Spanned,
visit_mut::{self, VisitMut},
Generics, Ident, Lifetime, Type, TypeBareFn, TypeReference,
};
use proc_macro2::Span;
use quote::ToTokens;
use crate::*;
use crate::{
common_tokens::FnPointerTokens,
ignored_wrapper::Ignored,
lifetimes::{LifetimeCounters, LifetimeIndex},
utils::{LinearResult, SynResultExt},
};
#[derive(Clone, Debug, PartialEq, Hash)]
pub(crate) struct FnInfo<'a> {
parent_generics: &'a Generics,
env_lifetimes: Vec<&'a Ident>,
initial_bound_lifetime: usize,
pub functions: Vec<Function<'a>>,
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub(crate) struct Function<'a> {
pub(crate) fn_token: syn::Token!(fn),
pub(crate) func_span: Ignored<Span>,
pub(crate) first_bound_lt: usize,
pub(crate) first_unnamed_bound_lt: usize,
pub(crate) named_bound_lts: Vec<&'a Ident>,
pub(crate) named_bound_lt_set: Ignored<HashSet<&'a Ident>>,
pub(crate) bound_lts_count: usize,
pub(crate) is_unsafe: bool,
pub(crate) bound_lt_spans: Ignored<Vec<Option<Span>>>,
pub(crate) params: Vec<FnParamRet<'a>>,
pub(crate) returns: Option<FnParamRet<'a>>,
}
#[derive(Clone, Debug, PartialEq, Hash)]
pub(crate) struct FnParamRet<'a> {
pub(crate) name: Option<&'a Ident>,
pub(crate) lifetime_refs: Vec<LifetimeIndex>,
pub(crate) ty: &'a Type,
pub(crate) param_or_ret: ParamOrReturn,
}
pub(crate) struct VisitFieldRet<'a> {
pub(crate) referenced_lifetimes: Vec<LifetimeIndex>,
pub(crate) functions: Vec<Function<'a>>,
}
#[allow(dead_code)]
impl<'a> TypeVisitor<'a> {
#[inline(never)]
pub fn new(arenas: &'a Arenas, ctokens: &'a FnPointerTokens, generics: &'a Generics) -> Self {
TypeVisitor {
refs: ImmutableRefs {
arenas,
ctokens,
env_generics: generics,
},
vars: Vars {
allow_type_macros: false,
referenced_lifetimes: Vec::default(),
fn_info: FnInfo {
parent_generics: generics,
env_lifetimes: generics.lifetimes().map(|lt| <.lifetime.ident).collect(),
initial_bound_lifetime: generics.lifetimes().count(),
functions: Vec::new(),
},
errors: LinearResult::ok(()),
},
}
}
pub fn allow_type_macros(&mut self) {
self.vars.allow_type_macros = true;
}
pub fn arenas(&self) -> &'a Arenas {
self.refs.arenas
}
pub fn ctokens(&self) -> &'a FnPointerTokens {
self.refs.ctokens
}
pub fn env_generics(&self) -> &'a Generics {
self.refs.env_generics
}
pub fn visit_field(&mut self, ty: &mut Type) -> VisitFieldRet<'a> {
self.visit_type_mut(ty);
VisitFieldRet {
referenced_lifetimes: mem::take(&mut self.vars.referenced_lifetimes),
functions: mem::take(&mut self.vars.fn_info.functions),
}
}
pub fn get_errors(&mut self) -> Result<(), syn::Error> {
self.vars.errors.take()
}
pub fn into_fn_info(self) -> FnInfo<'a> {
self.vars.fn_info
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ParamOrReturn {
Param,
Return,
}
pub(crate) struct TypeVisitor<'a> {
refs: ImmutableRefs<'a>,
vars: Vars<'a>,
}
#[allow(dead_code)]
#[derive(Copy, Clone)]
struct ImmutableRefs<'a> {
arenas: &'a Arenas,
ctokens: &'a FnPointerTokens,
env_generics: &'a Generics,
}
struct Vars<'a> {
allow_type_macros: bool,
referenced_lifetimes: Vec<LifetimeIndex>,
fn_info: FnInfo<'a>,
errors: LinearResult<()>,
}
struct FnVisitor<'a, 'b> {
refs: ImmutableRefs<'a>,
vars: &'b mut Vars<'a>,
lifetime_counts: LifetimeCounters,
current: Function<'a>,
param_ret: FnParamRetLifetimes,
}
struct FnParamRetLifetimes {
span: Span,
lifetime_refs: Vec<LifetimeIndex>,
param_or_ret: ParamOrReturn,
}
impl FnParamRetLifetimes {
fn new(param_or_ret: ParamOrReturn, span: Option<Span>) -> Self {
Self {
span: span.unwrap_or_else(Span::call_site),
lifetime_refs: Vec::new(),
param_or_ret,
}
}
}
impl<'a> Vars<'a> {
pub fn add_referenced_env_lifetime(&mut self, ind: LifetimeIndex) {
let is_env_lt = match (ind, ind.to_param()) {
(LifetimeIndex::STATIC, _) => true,
(_, Some(index)) => index < self.fn_info.env_lifetimes.len(),
_ => false,
};
if is_env_lt {
self.referenced_lifetimes.push(ind);
}
}
}
impl<'a> VisitMut for TypeVisitor<'a> {
#[inline(never)]
fn visit_type_bare_fn_mut(&mut self, func: &mut TypeBareFn) {
let ctokens = self.refs.ctokens;
let arenas = self.refs.arenas;
let func_span = func.span();
let is_unsafe = func.unsafety.is_some();
let abi = func.abi.as_ref().map(|x| x.name.as_ref());
const ABI_ERR: &str = "must write `extern \"C\" fn` for function pointer types.";
match abi {
Some(Some(abi)) if *abi == ctokens.c_abi_lit => {}
Some(Some(abi)) => {
self.vars
.errors
.push_err(spanned_err!(abi, "Abi not supported for function pointers",));
return;
}
Some(None) => {}
None => {
self.vars.errors.push_err(spanned_err!(
func,
"The default abi is not supported for function pointers,you {}`.",
ABI_ERR
));
return;
}
}
let named_bound_lts: Vec<&'a Ident> = func
.lifetimes
.take() .into_iter()
.flat_map(|lt| lt.lifetimes)
.map(|lt| arenas.alloc(lt.lifetime.ident))
.collect::<Vec<&'a Ident>>();
let named_bound_lt_set = named_bound_lts.iter().cloned().collect();
let first_bound_lt = self.vars.fn_info.initial_bound_lifetime;
let bound_lts_count = named_bound_lts.len();
let mut current_function = FnVisitor {
refs: self.refs,
vars: &mut self.vars,
lifetime_counts: LifetimeCounters::new(),
current: Function {
fn_token: func.fn_token,
func_span: Ignored::new(func_span),
first_bound_lt,
first_unnamed_bound_lt: first_bound_lt + named_bound_lts.len(),
bound_lts_count,
named_bound_lts,
named_bound_lt_set: Ignored::new(named_bound_lt_set),
bound_lt_spans: Ignored::new(vec![None; bound_lts_count]),
is_unsafe,
params: Vec::new(),
returns: None,
},
param_ret: FnParamRetLifetimes::new(ParamOrReturn::Param, None),
};
fn visit_param_ret<'a, 'b>(
this: &mut FnVisitor<'a, 'b>,
name: Option<&'a Ident>,
ty: &'a mut Type,
param_or_ret: ParamOrReturn,
) {
let ty_span = Some(ty.span());
this.param_ret = FnParamRetLifetimes::new(param_or_ret, ty_span);
this.visit_type_mut(ty);
let param_ret = mem::replace(
&mut this.param_ret,
FnParamRetLifetimes::new(param_or_ret, ty_span),
);
let param_ret = FnParamRet {
name,
lifetime_refs: param_ret.lifetime_refs,
ty,
param_or_ret: param_ret.param_or_ret,
};
match param_or_ret {
ParamOrReturn::Param => this.current.params.push(param_ret),
ParamOrReturn::Return => this.current.returns = Some(param_ret),
}
}
for (i, param) in func.inputs.iter_mut().enumerate() {
let arg_name = extract_fn_arg_name(i, param, arenas);
let ty = arenas.alloc_mut(param.ty.clone());
visit_param_ret(&mut current_function, arg_name, ty, ParamOrReturn::Param);
}
let tmp = match mem::replace(&mut func.output, syn::ReturnType::Default) {
syn::ReturnType::Default => None,
syn::ReturnType::Type(_, ty) => Some(arenas.alloc_mut(*ty)),
};
if let Some(ty) = tmp {
visit_param_ret(&mut current_function, None, ty, ParamOrReturn::Return);
}
let mut current = current_function.current;
current.anonimize_lifetimes(¤t_function.lifetime_counts, &mut self.vars.errors);
while func.inputs.pop().is_some() {}
self.vars.fn_info.functions.push(current);
}
#[inline(never)]
fn visit_lifetime_mut(&mut self, lt: &mut Lifetime) {
let ctokens = self.refs.ctokens;
let lt = <.ident;
if *lt == ctokens.static_ {
LifetimeIndex::STATIC
} else {
let env_lifetimes = self.vars.fn_info.env_lifetimes.iter();
let found_lt = env_lifetimes
.enumerate()
.position(|(_, lt_ident)| *lt_ident == lt);
match found_lt {
Some(index) => LifetimeIndex::Param(index as _),
None => {
self.vars
.errors
.push_err(spanned_err!(lt, "unknown lifetime"));
LifetimeIndex::NONE
}
}
}
.piped(|lt| self.vars.add_referenced_env_lifetime(lt))
}
fn visit_type_macro_mut(&mut self, i: &mut syn::TypeMacro) {
if !self.vars.allow_type_macros {
push_type_macro_err(&mut self.vars.errors, i);
}
}
}
impl<'a, 'b> FnVisitor<'a, 'b> {
#[inline(never)]
fn setup_lifetime(&mut self, lt: Option<&Ident>, span: Span) -> Option<&'a Ident> {
let ctokens = self.refs.ctokens;
let mut ret: Option<&'a Ident> = None;
if lt == Some(&ctokens.static_) {
LifetimeIndex::STATIC
} else if lt.is_none() || lt == Some(&ctokens.underscore) {
match self.param_ret.param_or_ret {
ParamOrReturn::Param => self.new_bound_lifetime(span),
ParamOrReturn::Return => match self.current.bound_lts_count {
0 => {
self.vars.errors.push_err(syn_err!(
span,
"attempted to use an elided lifetime in the \
return type when there are no lifetimes \
used in any parameter",
));
LifetimeIndex::NONE
}
1 => LifetimeIndex::Param(self.vars.fn_info.initial_bound_lifetime as _),
_ => {
self.vars.errors.push_err(syn_err!(
span,
"attempted to use an elided lifetime in the \
return type when there are multiple lifetimes used \
in parameters.",
));
LifetimeIndex::NONE
}
},
}
} else {
let lt = lt.expect("BUG");
let env_lts = self.vars.fn_info.env_lifetimes.iter();
let fn_lts = self.current.named_bound_lts.iter();
let found_lt = env_lts.chain(fn_lts).position(|ident| *ident == lt);
match found_lt {
Some(index) => {
if let Some(index) = index.checked_sub(self.current.first_bound_lt) {
self.current.bound_lt_spans[index].get_or_insert(span);
}
ret = Some(&ctokens.underscore);
LifetimeIndex::Param(index as _)
}
None => {
self.vars
.errors
.push_err(spanned_err!(lt, "unknown lifetime"));
LifetimeIndex::NONE
}
}
}
.piped(|li| {
self.param_ret.lifetime_refs.push(li);
self.lifetime_counts.increment(li);
});
ret
}
fn new_bound_lifetime(&mut self, span: Span) -> LifetimeIndex {
let index = self.vars.fn_info.initial_bound_lifetime + self.current.bound_lts_count;
self.current.bound_lt_spans.push(Some(span));
self.current.bound_lts_count += 1;
LifetimeIndex::Param(index as _)
}
}
impl<'a, 'b> VisitMut for FnVisitor<'a, 'b> {
#[inline(never)]
fn visit_type_bare_fn_mut(&mut self, func: &mut TypeBareFn) {
self.vars.errors.push_err(syn_err!(
self.param_ret.span,
"\n\
This library does not currently support nested function pointers.\n\
To use the function pointer as a parameter define a wrapper type:\n\
\t#[derive(StableAbi)]\n\
\t#[repr(transparent)] \n\
\tpub struct CallbackParam{{ \n\
\t\tpub func:{func}\n\
\t}}\n\
\n\
",
func = func.to_token_stream()
))
}
fn visit_type_reference_mut(&mut self, ref_: &mut TypeReference) {
let _ctokens = self.refs.ctokens;
let lt = ref_.lifetime.as_ref().map(|x| &x.ident);
if let Some(ident) = self.setup_lifetime(lt, ref_.and_token.span()).cloned() {
if let Some(lt) = &mut ref_.lifetime {
lt.ident = ident
}
}
visit_mut::visit_type_mut(self, &mut ref_.elem)
}
fn visit_lifetime_mut(&mut self, lt: &mut Lifetime) {
if let Some(ident) = self.setup_lifetime(Some(<.ident), lt.apostrophe.span()) {
lt.ident = ident.clone();
}
}
fn visit_type_macro_mut(&mut self, i: &mut syn::TypeMacro) {
if !self.vars.allow_type_macros {
push_type_macro_err(&mut self.vars.errors, i);
}
}
}
fn extract_fn_arg_name<'a>(
_index: usize,
arg: &mut syn::BareFnArg,
arenas: &'a Arenas,
) -> Option<&'a Ident> {
match arg.name.take() {
Some((name, _)) => Some(arenas.alloc(name)),
None => None,
}
}
impl<'a> Function<'a> {
fn anonimize_lifetimes(
&mut self,
lifetime_counts: &LifetimeCounters,
errors: &mut Result<(), syn::Error>,
) {
let first_bound_lt = self.first_bound_lt;
let mut current_lt = first_bound_lt;
let asigned_lts = (0..self.bound_lts_count)
.map(|i| {
let lt_i: usize = first_bound_lt + i;
if lifetime_counts.get(LifetimeIndex::Param(lt_i)) <= 1 {
LifetimeIndex::ANONYMOUS
} else {
if current_lt == LifetimeIndex::MAX_LIFETIME_PARAM + 1 {
errors.push_err(syn_err!(
self.bound_lt_spans[i].unwrap_or(*self.func_span),
"Cannot have more than {} non-static lifetimes \
(except for lifetimes only used once inside \
function pointer types)",
LifetimeIndex::MAX_LIFETIME_PARAM + 1
));
}
let ret = LifetimeIndex::Param(current_lt);
current_lt += 1;
ret
}
})
.collect::<Vec<LifetimeIndex>>();
for params in &mut self.params {
for p_lt in &mut params.lifetime_refs {
let param = match p_lt.to_param() {
Some(param) => (param).wrapping_sub(first_bound_lt),
None => continue,
};
if let Some(assigned) = asigned_lts.get(param) {
*p_lt = *assigned;
}
}
}
}
}
fn push_type_macro_err(res: &mut Result<(), syn::Error>, i: &syn::TypeMacro) {
res.push_err(spanned_err!(
i,
"\
Cannot currently use type macros safely.
To enable use of type macros use the `#[sabi(unsafe_allow_type_macros)]` attribute.
The reason this is unsafe to enable them is because StableAbi cannot currently
analize the lifetimes within macros,
which means that if any lifetime argument inside the macro invocation changes
it won't be checked by the runtime type checker.
"
));
}