use std::{fmt, rc::Rc};
use prebindgen::SourceLocation;
use quote::ToTokens;
use super::{
array_len::{lower_array_len, ArrayExtent, ConstIndex, UnsupportedArrayLen},
key::TypeKey,
origin::Origin,
};
#[derive(Clone, Debug)]
pub struct TypeRef {
pub(super) kind: TypeKind,
pub(super) origin: Origin<syn::Type>,
}
impl fmt::Display for TypeRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.key().as_str())
}
}
impl TypeRef {
pub fn kind(&self) -> &TypeKind {
&self.kind
}
pub fn spell(&self) -> proc_macro2::TokenStream {
self.origin.spell()
}
#[allow(dead_code)]
pub(crate) fn as_syn(&self) -> &syn::Type {
self.origin.as_syn()
}
pub fn location(&self) -> &SourceLocation {
&self.origin.location
}
pub(crate) fn origin_with<S>(&self, syntax: S) -> Origin<S> {
self.origin.with(syntax)
}
}
impl TypeRef {
pub fn layer_stack(&self) -> (crate::shape::Shape, &TypeRef) {
use crate::shape::Shape;
let mut core = self;
let optional = matches!(core.unwrapped().kind, TypeKind::Optional(_));
if let TypeKind::Optional(inner) = &core.unwrapped().kind {
core = inner;
}
let iterable = matches!(core.unwrapped().kind, TypeKind::Vec(_) | TypeKind::Slice(_));
if let TypeKind::Vec(inner) | TypeKind::Slice(inner) = &core.unwrapped().kind {
core = inner;
}
let mut shape = Shape::Base;
if iterable {
shape = Shape::iterable(shape);
}
if optional {
shape = Shape::optional((), shape);
}
(shape, core)
}
pub fn layer_types(&self) -> Vec<&TypeRef> {
let mut out = vec![self];
let mut cur = self;
if let TypeKind::Optional(inner) = &cur.unwrapped().kind {
out.push(inner);
cur = inner;
}
if let TypeKind::Vec(inner) | TypeKind::Slice(inner) = &cur.unwrapped().kind {
out.push(inner);
}
out
}
pub fn unwrapped(&self) -> &TypeRef {
match &self.kind {
TypeKind::Boxed(inner) | TypeKind::Cow { inner, .. } => inner.unwrapped(),
_ => self,
}
}
pub fn optional_inner(&self) -> Option<&TypeRef> {
match &self.unwrapped().kind {
TypeKind::Optional(inner) => Some(inner),
_ => None,
}
}
pub fn sequence_elem(&self) -> Option<&TypeRef> {
match &self.unwrapped().kind {
TypeKind::Vec(elem) | TypeKind::Slice(elem) => Some(elem),
_ => None,
}
}
pub fn borrow_target(&self) -> Option<&TypeRef> {
let inner = match &self.unwrapped().kind {
TypeKind::Ref { inner, .. } => inner,
_ => return None,
};
Some(match &inner.kind {
TypeKind::Uninit(slot) => slot,
_ => inner,
})
}
pub fn borrowed(&self) -> TypeRef {
let inner = self.origin.spell();
TypeRef {
kind: TypeKind::Ref {
lifetime: None,
mutable: false,
inner: Box::new(self.clone()),
},
origin: self.origin.with(syn::parse_quote!(&#inner)),
}
}
pub fn optional(&self) -> TypeRef {
let inner = self.origin.spell();
TypeRef {
kind: TypeKind::Optional(Box::new(self.clone())),
origin: self.origin.with(syn::parse_quote!(Option<#inner>)),
}
}
pub fn scalar(kind: ScalarKind) -> TypeRef {
let ident = syn::Ident::new(kind.as_str(), proc_macro2::Span::call_site());
TypeRef {
kind: TypeKind::Scalar(kind),
origin: Origin::new(
syn::parse_quote!(#ident),
std::rc::Rc::new(prebindgen::SourceLocation::default()),
),
}
}
pub(super) fn named(ident: &syn::Ident) -> TypeRef {
TypeRef {
kind: TypeKind::Named {
id: TypeId {
name: ident.to_string(),
},
args: Vec::new(),
},
origin: Origin::new(
syn::parse_quote!(#ident),
std::rc::Rc::new(prebindgen::SourceLocation::default()),
),
}
}
pub fn key(&self) -> TypeKey {
TypeKey::from_type(self.origin.as_syn())
}
pub fn erased_wrapper(&self) -> Option<&'static str> {
self.erased_wrappers().into_iter().next()
}
pub fn erased_wrappers(&self) -> Vec<&'static str> {
let mut names = Vec::new();
let mut ty = self;
loop {
let name = match &ty.kind {
TypeKind::Boxed(inner) => {
ty = inner;
"Box"
}
TypeKind::Cow { inner, .. } => {
ty = inner;
"Cow"
}
_ => return names,
};
names.push(name);
}
}
pub fn stripped_key(&self) -> TypeKey {
TypeKey::from_type(&self.stripped_syntax())
}
pub(crate) fn stripped_syntax(&self) -> syn::Type {
self.unwrapped().origin.as_syn().clone()
}
pub fn is_exclusive_borrow(&self) -> bool {
matches!(
&self.unwrapped().kind,
TypeKind::Ref { mutable: true, inner, .. } if !matches!(inner.kind, TypeKind::Uninit(_))
)
}
pub fn fallible_parts(&self) -> Option<(&TypeRef, &TypeRef)> {
match &self.unwrapped().kind {
TypeKind::Fallible { ok, err } => Some((ok, err)),
_ => None,
}
}
pub fn callback_args(&self) -> Option<&[TypeRef]> {
match &self.unwrapped().kind {
TypeKind::Callback { args } => Some(args),
_ => None,
}
}
pub fn array_extent(&self) -> Option<&ArrayExtent> {
match &self.unwrapped().kind {
TypeKind::Array { extent, .. } => Some(extent),
_ => None,
}
}
pub fn extents(&self) -> Vec<&ArrayExtent> {
let mut out = Vec::new();
self.collect_extents(&mut out);
out
}
pub(super) fn first_unresolved(
&self,
declared: &std::collections::HashSet<String>,
) -> Option<String> {
match &self.kind {
TypeKind::Named { id, .. } => (!declared.contains(&id.name)).then(|| id.name.clone()),
TypeKind::Optional(t)
| TypeKind::Vec(t)
| TypeKind::Slice(t)
| TypeKind::Boxed(t)
| TypeKind::Uninit(t)
| TypeKind::Cow { inner: t, .. }
| TypeKind::Ref { inner: t, .. } => t.first_unresolved(declared),
TypeKind::Array { elem, .. } => elem.first_unresolved(declared),
TypeKind::Fallible { ok, err } => ok
.first_unresolved(declared)
.or_else(|| err.first_unresolved(declared)),
TypeKind::Callback { args } => args.iter().find_map(|a| a.first_unresolved(declared)),
TypeKind::Scalar(_) | TypeKind::Str | TypeKind::String | TypeKind::Unit => None,
}
}
pub fn walk(&self) -> Vec<&TypeRef> {
let mut out = Vec::new();
self.collect_refs(&mut out);
out
}
fn collect_refs<'a>(&'a self, out: &mut Vec<&'a TypeRef>) {
out.push(self);
match &self.unwrapped().kind {
TypeKind::Ref { .. } => {
if let Some(t) = self.borrow_target() {
t.collect_refs(out)
}
}
TypeKind::Optional(t) | TypeKind::Vec(t) | TypeKind::Slice(t) | TypeKind::Uninit(t) => {
t.collect_refs(out)
}
TypeKind::Array { elem, .. } => elem.collect_refs(out),
TypeKind::Fallible { ok, err } => {
ok.collect_refs(out);
err.collect_refs(out);
}
TypeKind::Callback { args } => args.iter().for_each(|t| t.collect_refs(out)),
TypeKind::Named { .. }
| TypeKind::Scalar(_)
| TypeKind::Str
| TypeKind::String
| TypeKind::Unit => {}
TypeKind::Boxed(_) | TypeKind::Cow { .. } => unreachable!(),
}
}
fn collect_extents<'a>(&'a self, out: &mut Vec<&'a ArrayExtent>) {
match &self.unwrapped().kind {
TypeKind::Array { elem, extent } => {
out.push(extent);
elem.collect_extents(out);
}
TypeKind::Optional(t)
| TypeKind::Vec(t)
| TypeKind::Slice(t)
| TypeKind::Uninit(t)
| TypeKind::Ref { inner: t, .. } => t.collect_extents(out),
TypeKind::Fallible { ok, err } => {
ok.collect_extents(out);
err.collect_extents(out);
}
TypeKind::Callback { args } => args.iter().for_each(|t| t.collect_extents(out)),
TypeKind::Named { .. }
| TypeKind::Scalar(_)
| TypeKind::Str
| TypeKind::String
| TypeKind::Unit => {}
TypeKind::Boxed(_) | TypeKind::Cow { .. } => unreachable!(),
}
}
}
#[derive(Clone, Debug)]
pub enum TypeKind {
Scalar(ScalarKind),
Str,
String,
Optional(Box<TypeRef>),
Vec(Box<TypeRef>),
Slice(Box<TypeRef>),
Fallible { ok: Box<TypeRef>, err: Box<TypeRef> },
Named { id: TypeId, args: Vec<GenericArg> },
Array {
elem: Box<TypeRef>,
extent: Box<ArrayExtent>,
},
Ref {
lifetime: Option<syn::Lifetime>,
mutable: bool,
inner: Box<TypeRef>,
},
Boxed(Box<TypeRef>),
Cow {
lifetime: syn::Lifetime,
inner: Box<TypeRef>,
},
Uninit(Box<TypeRef>),
Callback { args: Vec<TypeRef> },
Unit,
}
#[derive(Clone, Debug)]
pub enum GenericArg {
Lifetime(syn::Lifetime),
Type(Box<TypeRef>),
}
impl TypeKind {
#[allow(dead_code)]
pub(crate) fn to_syn(&self) -> syn::Type {
let opt_lifetime =
|l: &Option<syn::Lifetime>| l.as_ref().map(|l| quote::quote!(#l)).unwrap_or_default();
match self {
Self::Scalar(k) => {
let ident = syn::Ident::new(k.as_str(), proc_macro2::Span::call_site());
syn::parse_quote!(#ident)
}
Self::Str => syn::parse_quote!(str),
Self::String => syn::parse_quote!(String),
Self::Optional(t) => {
let inner = t.kind.to_syn();
syn::parse_quote!(Option<#inner>)
}
Self::Vec(t) => {
let inner = t.kind.to_syn();
syn::parse_quote!(Vec<#inner>)
}
Self::Slice(t) => {
let inner = t.kind.to_syn();
syn::parse_quote!([#inner])
}
Self::Boxed(t) => {
let inner = t.kind.to_syn();
syn::parse_quote!(Box<#inner>)
}
Self::Uninit(t) => {
let inner = t.kind.to_syn();
syn::parse_quote!(MaybeUninit<#inner>)
}
Self::Cow { lifetime, inner } => {
let inner = inner.kind.to_syn();
syn::parse_quote!(Cow<#lifetime, #inner>)
}
Self::Fallible { ok, err } => {
let (ok, err) = (ok.kind.to_syn(), err.kind.to_syn());
syn::parse_quote!(Result<#ok, #err>)
}
Self::Ref {
lifetime,
mutable,
inner,
} => {
let lt = opt_lifetime(lifetime);
let mutability = mutable.then(|| quote::quote!(mut)).unwrap_or_default();
let inner = inner.kind.to_syn();
syn::parse_quote!(& #lt #mutability #inner)
}
Self::Array { elem, extent } => {
let elem = elem.kind.to_syn();
let len = extent.origin.spell();
syn::parse_quote!([#elem; #len])
}
Self::Named { id, args } => {
let mut path: syn::Path =
syn::parse_str(&id.name).expect("a name this model built from a path");
if !args.is_empty() {
let args = args.iter().map(|a| match a {
GenericArg::Lifetime(l) => quote::quote!(#l),
GenericArg::Type(t) => {
let t = t.kind.to_syn();
quote::quote!(#t)
}
});
let last = path.segments.last_mut().expect("a non-empty path");
last.arguments =
syn::PathArguments::AngleBracketed(syn::parse_quote!(<#(#args),*>));
}
syn::parse_quote!(#path)
}
Self::Callback { args } => {
let args = args.iter().map(|a| a.kind.to_syn());
syn::parse_quote!(impl Fn(#(#args),*) + Send + Sync + 'static)
}
Self::Unit => syn::parse_quote!(()),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TypeId {
pub name: String,
}
impl TypeId {
pub fn ident(&self) -> Option<syn::Ident> {
syn::parse_str::<syn::Ident>(&self.name).ok()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ScalarKind {
Bool,
I8,
I16,
I32,
I64,
Isize,
U8,
U16,
U32,
U64,
Usize,
F32,
F64,
}
impl ScalarKind {
fn from_name(name: &str) -> Option<Self> {
Some(match name {
"bool" => Self::Bool,
"i8" => Self::I8,
"i16" => Self::I16,
"i32" => Self::I32,
"i64" => Self::I64,
"isize" => Self::Isize,
"u8" => Self::U8,
"u16" => Self::U16,
"u32" => Self::U32,
"u64" => Self::U64,
"usize" => Self::Usize,
"f32" => Self::F32,
"f64" => Self::F64,
_ => return None,
})
}
pub fn as_str(self) -> &'static str {
match self {
Self::Bool => "bool",
Self::I8 => "i8",
Self::I16 => "i16",
Self::I32 => "i32",
Self::I64 => "i64",
Self::Isize => "isize",
Self::U8 => "u8",
Self::U16 => "u16",
Self::U32 => "u32",
Self::U64 => "u64",
Self::Usize => "usize",
Self::F32 => "f32",
Self::F64 => "f64",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnsupportedType {
pub offending: String,
pub reason: UnsupportedTypeReason,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum UnsupportedTypeReason {
UnsupportedForm,
DisallowedImplTrait,
WrongGenericArity { expected: usize },
WrongGenericArguments { expected: &'static str },
UnsupportedTuple,
OwnedUninit,
SharedUninit,
AssociatedType,
UnsupportedGenericArgument,
BadArrayExtent(Box<UnsupportedArrayLen>),
}
impl fmt::Display for UnsupportedType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.reason {
UnsupportedTypeReason::BadArrayExtent(e) => return write!(f, "{e}"),
UnsupportedTypeReason::UnsupportedForm => write!(
f,
"type `{}` is a form the prebindgen source language does not accept",
self.offending
),
UnsupportedTypeReason::DisallowedImplTrait => write!(
f,
"type `{}` is not an accepted callback — the only `impl Trait` in the language is \
`impl Fn(..) + Send + Sync + 'static` returning `()`",
self.offending
),
UnsupportedTypeReason::WrongGenericArity { expected } => write!(
f,
"type `{}` needs exactly {expected} type argument(s)",
self.offending
),
UnsupportedTypeReason::WrongGenericArguments { expected } => write!(
f,
"type `{}` is not the shape `{expected}` \u{2014} its arguments are the ones \
that type takes, in the order it takes them",
self.offending
),
UnsupportedTypeReason::UnsupportedTuple => write!(
f,
"type `{}` is a tuple; only the unit `()` is supported — return the \
components separately, or wrap them in a `#[prebindgen]` struct",
self.offending
),
UnsupportedTypeReason::OwnedUninit => write!(
f,
"type `{}` is uninitialized storage outside an out-parameter. Only `&mut \
MaybeUninit<T>` means anything at a boundary \u{2014} it says the caller supplies \
the slot and the callee fills it; owned or in a field it promises nothing, and \
reading it would be undefined",
self.offending
),
UnsupportedTypeReason::SharedUninit => write!(
f,
"type `{}` is a shared borrow of uninitialized storage: `&T` promises a readable \
`T`, which this may not be. Use `&mut MaybeUninit<T>` for an out-parameter",
self.offending
),
UnsupportedTypeReason::AssociatedType => write!(
f,
"type `{}` is an associated type; `#[prebindgen]` never captures `impl` \
blocks, so its resolution is unknowable here — name the concrete type",
self.offending
),
UnsupportedTypeReason::UnsupportedGenericArgument => write!(
f,
"type `{}` has a generic argument that is neither a type nor a lifetime",
self.offending
),
}?;
write!(f, " — see docs/source-language.md for the accepted grammar")
}
}
impl std::error::Error for UnsupportedType {}
pub const TRANSPARENT_WRAPPERS: &[&str] = &["Box", "Cow"];
pub fn peel_transparent(ty: &syn::Type) -> Option<(&'static str, syn::Type)> {
let syn::Type::Path(tp) = ty else { return None };
let seg = tp.path.segments.last()?;
let name = TRANSPARENT_WRAPPERS.iter().find(|w| seg.ident == **w)?;
let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
return None;
};
ab.args.iter().find_map(|a| match a {
syn::GenericArgument::Type(inner) => Some((*name, inner.clone())),
_ => None,
})
}
pub(crate) fn lower_type(
ty: &syn::Type,
consts: &ConstIndex,
at: &Rc<SourceLocation>,
) -> Result<TypeRef, UnsupportedType> {
let fail = |reason| UnsupportedType {
offending: ty.to_token_stream().to_string(),
reason,
};
let kind = match ty {
syn::Type::Group(g) => return lower_type(&g.elem, consts, at),
syn::Type::Paren(p) => return lower_type(&p.elem, consts, at),
syn::Type::Reference(r) => {
let inner = match maybe_uninit_inner(&r.elem) {
Some(uninit) if r.mutability.is_some() => TypeRef {
kind: TypeKind::Uninit(Box::new(lower_type(&uninit, consts, at)?)),
origin: Origin::new((*r.elem).clone(), Rc::clone(at)),
},
Some(_) => return Err(fail(UnsupportedTypeReason::SharedUninit)),
None => lower_type(&r.elem, consts, at)?,
};
TypeKind::Ref {
lifetime: r.lifetime.clone(),
mutable: r.mutability.is_some(),
inner: Box::new(inner),
}
}
syn::Type::Slice(s) => TypeKind::Slice(Box::new(lower_type(&s.elem, consts, at)?)),
_ if is_unit_type(ty) => TypeKind::Unit,
syn::Type::Tuple(_) => return Err(fail(UnsupportedTypeReason::UnsupportedTuple)),
syn::Type::Array(a) => {
let rendered = a.to_token_stream().to_string();
let extent = lower_array_len(&a.len, &rendered, at, consts)
.map_err(|e| fail(UnsupportedTypeReason::BadArrayExtent(Box::new(e))))?;
TypeKind::Array {
elem: Box::new(lower_type(&a.elem, consts, at)?),
extent: Box::new(extent),
}
}
syn::Type::ImplTrait(_) => match super::extract_fn_trait_args(ty) {
Some(args) => TypeKind::Callback {
args: args
.iter()
.map(|a| lower_type(a, consts, at))
.collect::<Result<_, _>>()?,
},
None => return Err(fail(UnsupportedTypeReason::DisallowedImplTrait)),
},
syn::Type::Path(tp) => lower_path(ty, tp, consts, at)?,
_ => return Err(fail(UnsupportedTypeReason::UnsupportedForm)),
};
Ok(TypeRef {
kind,
origin: Origin::new(ty.clone(), Rc::clone(at)),
})
}
fn lower_path(
ty: &syn::Type,
tp: &syn::TypePath,
consts: &ConstIndex,
at: &Rc<SourceLocation>,
) -> Result<TypeKind, UnsupportedType> {
let fail = |reason| UnsupportedType {
offending: ty.to_token_stream().to_string(),
reason,
};
if tp.qself.is_some() {
return Err(fail(UnsupportedTypeReason::AssociatedType));
}
let Some(last) = tp.path.segments.last() else {
return Err(fail(UnsupportedTypeReason::UnsupportedForm));
};
let name = last.ident.to_string();
let mut has_lifetime_arg = false;
let args: Vec<GenericArg> = match &last.arguments {
syn::PathArguments::None => Vec::new(),
syn::PathArguments::AngleBracketed(ab) => {
let mut out = Vec::new();
for a in &ab.args {
match a {
syn::GenericArgument::Type(t) => {
out.push(GenericArg::Type(Box::new(lower_type(t, consts, at)?)));
}
syn::GenericArgument::Lifetime(l) => {
has_lifetime_arg = true;
out.push(GenericArg::Lifetime(l.clone()));
}
_ => return Err(fail(UnsupportedTypeReason::UnsupportedGenericArgument)),
}
}
out
}
syn::PathArguments::Parenthesized(_) => {
return Err(fail(UnsupportedTypeReason::UnsupportedForm))
}
};
if tp
.path
.segments
.iter()
.rev()
.skip(1)
.any(|s| !matches!(s.arguments, syn::PathArguments::None))
{
return Err(fail(UnsupportedTypeReason::UnsupportedForm));
}
let is_bare = tp.path.leading_colon.is_none() && tp.path.segments.len() == 1;
if is_bare {
if args.is_empty() {
if let Some(kind) = ScalarKind::from_name(&name) {
return Ok(TypeKind::Scalar(kind));
}
match name.as_str() {
"String" => return Ok(TypeKind::String),
"str" => return Ok(TypeKind::Str),
_ => {}
}
}
if !has_lifetime_arg || name == "Cow" {
let mut types: Vec<TypeRef> = args
.iter()
.filter_map(|a| match a {
GenericArg::Type(t) => Some((**t).clone()),
GenericArg::Lifetime(_) => None,
})
.collect();
let arity = |n: usize| {
if types.len() == n {
Ok(())
} else {
Err(fail(UnsupportedTypeReason::WrongGenericArity {
expected: n,
}))
}
};
match name.as_str() {
"Option" => {
arity(1)?;
return Ok(TypeKind::Optional(Box::new(types.remove(0))));
}
"Vec" => {
arity(1)?;
return Ok(TypeKind::Vec(Box::new(types.remove(0))));
}
"Box" => {
arity(1)?;
return Ok(TypeKind::Boxed(Box::new(types.remove(0))));
}
"Cow" => {
let [GenericArg::Lifetime(lifetime), GenericArg::Type(inner)] = &args[..]
else {
return Err(fail(UnsupportedTypeReason::WrongGenericArguments {
expected: "Cow<'a, T>",
}));
};
return Ok(TypeKind::Cow {
lifetime: lifetime.clone(),
inner: inner.clone(),
});
}
"MaybeUninit" => return Err(fail(UnsupportedTypeReason::OwnedUninit)),
"Result" => {
arity(2)?;
let err = Box::new(types.remove(1));
let ok = Box::new(types.remove(0));
return Ok(TypeKind::Fallible { ok, err });
}
_ => return Ok(named(tp, args)),
}
}
}
Ok(named(tp, args))
}
fn maybe_uninit_inner(ty: &syn::Type) -> Option<syn::Type> {
let syn::Type::Path(tp) = ty else { return None };
if tp.qself.is_some() || tp.path.leading_colon.is_some() || tp.path.segments.len() != 1 {
return None;
}
let seg = &tp.path.segments[0];
if seg.ident != "MaybeUninit" {
return None;
}
let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
return None;
};
match ab.args.first() {
Some(syn::GenericArgument::Type(t)) if ab.args.len() == 1 => Some(t.clone()),
_ => None,
}
}
pub(crate) fn is_unit_type(ty: &syn::Type) -> bool {
match ty {
syn::Type::Tuple(t) => t.elems.is_empty(),
syn::Type::Paren(p) => is_unit_type(&p.elem),
syn::Type::Group(g) => is_unit_type(&g.elem),
_ => false,
}
}
fn named(tp: &syn::TypePath, args: Vec<GenericArg>) -> TypeKind {
let mut name = String::new();
if tp.path.leading_colon.is_some() {
name.push_str("::");
}
name.push_str(
&tp.path
.segments
.iter()
.map(|s| s.ident.to_string())
.collect::<Vec<_>>()
.join("::"),
);
TypeKind::Named {
id: TypeId { name },
args,
}
}