use std::{fmt, rc::Rc};
use quote::ToTokens;
mod array_len;
mod element;
pub mod emit;
mod key;
mod origin;
pub(crate) mod spell;
pub(crate) mod spelling;
mod ty;
#[cfg(test)]
mod tests;
use prebindgen::SourceLocation;
use self::{array_len::ConstIndex, ty::lower_type};
pub use self::{
array_len::{ArrayExtent, ArrayLenReason, ConstId, ExtentSource, UnsupportedArrayLen},
element::{
Alternative, Constant, Element, Enum, EnumValue, Extern, Field, Function, Guard, Param,
Struct, Type, Unsupported, Variant,
},
key::{TypeKey, TypeKeyParseError},
origin::Origin,
spelling::{canonical_spelling, canonical_type},
ty::{
peel_transparent, GenericArg, ScalarKind, TypeId, TypeKind, TypeRef, UnsupportedType,
UnsupportedTypeReason, TRANSPARENT_WRAPPERS,
},
};
#[derive(Debug, Default, Clone)]
pub struct FlatBuilder {
items: Vec<(syn::Item, SourceLocation)>,
}
impl FlatBuilder {
pub fn source<P: AsRef<std::path::Path>>(self, dir: P) -> Self {
let source = prebindgen::Source::new(dir);
self.items(source.items_all())
}
pub fn source_named<P: AsRef<std::path::Path>>(
self,
dir: P,
crate_name: impl Into<String>,
) -> Self {
let source = prebindgen::Source::builder(dir)
.crate_name(crate_name)
.build();
self.items(source.items_all())
}
pub fn items<I>(mut self, items: I) -> Self
where
I: IntoIterator<Item = (syn::Item, SourceLocation)>,
{
self.items.extend(items);
self
}
pub fn build(self) -> Result<Flat, ParseError> {
let mut items = self.items;
let normalization = crate::flat::spelling::Normalization::from_items(&items);
for (item, _) in &mut items {
crate::flat::spelling::normalize_item_types(item, &normalization);
}
let consts = ConstIndex::new(items.iter().filter_map(|(item, loc)| match item {
syn::Item::Const(c) if c.ident != "_" => Some((
c.ident.to_string(),
(*c.expr).clone(),
loc.crate_name.clone(),
)),
_ => None,
}));
let mut elements: Vec<Element> = Vec::with_capacity(items.len());
let mut seen: Vec<(syn::Ident, SourceLocation)> = Vec::new();
for (item, loc) in items {
let element = lower_item(item, loc, &consts);
if let Some(name) = element.name() {
if let Some((first_name, first)) = seen.iter().find(|(n, _)| n == name) {
return Err(ParseError::DuplicateName(Box::new(DuplicateName {
name: first_name.clone(),
first: first.clone(),
second: element.location().clone(),
first_crate: first.crate_name.clone(),
second_crate: element.location().crate_name.clone(),
})));
}
seen.push((name.clone(), element.location().clone()));
}
elements.push(element);
}
resolve_references(&mut elements);
let by_name = elements
.iter()
.enumerate()
.filter_map(|(i, e)| e.name().map(|n| (n.to_string(), i)))
.collect();
let mut source_modules: Vec<String> = Vec::new();
for element in &elements {
if let Some(crate_name) = element.location().crate_name.as_ref() {
let module = crate_name.replace('-', "_");
if !source_modules.contains(&module) {
source_modules.push(module);
}
}
}
let mut flat = Flat {
elements,
by_name,
source_modules,
by_type: std::collections::HashMap::new(),
};
for i in 0..flat.elements.len() {
flat.index_types_of(i);
}
Ok(flat)
}
}
#[derive(Debug, Default)]
pub struct Flat {
elements: Vec<Element>,
source_modules: Vec<String>,
by_name: std::collections::HashMap<String, usize>,
by_type: std::collections::HashMap<String, TypeRef>,
}
pub trait Name: sealed::Sealed {
fn as_name(&self) -> std::borrow::Cow<'_, str>;
}
mod sealed {
pub trait Sealed {}
impl Sealed for str {}
impl Sealed for String {}
impl Sealed for syn::Ident {}
impl<T: ?Sized + Sealed> Sealed for &T {}
}
impl Name for str {
fn as_name(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Borrowed(self)
}
}
impl Name for String {
fn as_name(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Borrowed(self)
}
}
impl Name for syn::Ident {
fn as_name(&self) -> std::borrow::Cow<'_, str> {
std::borrow::Cow::Owned(self.to_string())
}
}
impl<T: ?Sized + Name> Name for &T {
fn as_name(&self) -> std::borrow::Cow<'_, str> {
T::as_name(self)
}
}
impl Flat {
pub fn builder() -> FlatBuilder {
FlatBuilder { items: Vec::new() }
}
pub fn elements(&self) -> impl Iterator<Item = &Element> {
self.elements.iter()
}
pub fn element<N: Name + ?Sized>(&self, name: &N) -> Option<&Element> {
self.elements
.get(*self.by_name.get(name.as_name().as_ref())?)
}
pub fn function<N: Name + ?Sized>(&self, name: &N) -> Option<&Function> {
match self.element(name)? {
Element::Function(f) => Some(f),
_ => None,
}
}
pub fn declared_type<N: Name + ?Sized>(&self, name: &N) -> Option<&Type> {
match self.element(name)? {
Element::Type(t) => Some(t),
_ => None,
}
}
pub fn constant<N: Name + ?Sized>(&self, name: &N) -> Option<&Constant> {
match self.element(name)? {
Element::Constant(c) => Some(c),
_ => None,
}
}
pub fn functions(&self) -> impl Iterator<Item = &Function> {
self.elements.iter().filter_map(|e| match e {
Element::Function(f) => Some(f),
_ => None,
})
}
pub fn types(&self) -> impl Iterator<Item = &Type> {
self.elements.iter().filter_map(|e| match e {
Element::Type(t) => Some(t),
_ => None,
})
}
pub fn constants(&self) -> impl Iterator<Item = &Constant> {
self.elements.iter().filter_map(|e| match e {
Element::Constant(c) => Some(c),
_ => None,
})
}
pub fn struct_type<N: Name + ?Sized>(&self, name: &N) -> Option<&Struct> {
match self.declared_type(name)? {
Type::Struct(s) => Some(s),
_ => None,
}
}
#[allow(dead_code)]
pub fn enum_item<N: Name + ?Sized>(&self, name: &N) -> Option<&syn::ItemEnum> {
match self.declared_type(name)? {
Type::Variant(v) => Some(v.origin.as_syn()),
Type::Enum(e) => Some(e.origin.as_syn()),
_ => None,
}
}
pub fn source_modules(&self) -> &[String] {
&self.source_modules
}
pub fn guards(&self) -> impl Iterator<Item = &Guard> {
self.elements.iter().filter_map(|e| match e {
Element::Guard(g) => Some(g),
_ => None,
})
}
pub fn type_ref(&self, ty: &syn::Type) -> Option<&TypeRef> {
self.by_type.get(&crate::flat::canonical_spelling(ty))
}
pub fn classify(&self, ty: &syn::Type) -> Result<TypeRef, UnsupportedType> {
if let Some(indexed) = self.type_ref(ty) {
return Ok(indexed.clone());
}
let consts = ConstIndex::new(self.constants().map(|c| {
(
c.name.to_string(),
(*c.origin.as_syn().expr).clone(),
c.origin.crate_name().map(str::to_owned),
)
}));
let at = Rc::new(SourceLocation::default());
lower_type(ty, &consts, &at)
}
fn index_types_of(&mut self, pos: usize) {
let refs: Vec<TypeRef> = element_type_refs(&self.elements[pos])
.into_iter()
.flat_map(TypeRef::walk)
.cloned()
.collect();
for ty in refs {
self.by_type
.entry(crate::flat::canonical_spelling(ty.origin.as_syn()))
.or_insert(ty);
}
}
pub fn unsupported(&self) -> impl Iterator<Item = &Unsupported> {
self.elements.iter().filter_map(|e| match e {
Element::Unsupported(u) => Some(u),
_ => None,
})
}
pub fn lower_signature(&self, f: &syn::ItemFn) -> Result<Function, ItemError> {
let consts = ConstIndex::new(self.constants().map(|c| {
(
c.name.to_string(),
(*c.origin.as_syn().expr).clone(),
c.origin.crate_name().map(str::to_owned),
)
}));
let at = Rc::new(SourceLocation::default());
lower_fn(f, &at, &consts)
}
pub fn add_local_function(&mut self, mut f: Function, crate_name: String) {
f.origin.location = Rc::new(SourceLocation {
crate_name: Some(crate_name),
..SourceLocation::default()
});
self.by_name.insert(f.name.to_string(), self.elements.len());
self.elements.push(Element::Function(f));
self.index_types_of(self.elements.len() - 1);
}
pub fn resolve(&self, id: &TypeId) -> Option<&Type> {
self.declared_type(&id.name)
}
}
fn resolve_references(elements: &mut [Element]) {
let mut declared: std::collections::HashSet<String> = elements
.iter()
.filter_map(|e| match e {
Element::Type(t) => Some(t.name().to_string()),
_ => None,
})
.collect();
loop {
let mut refused = Vec::new();
for (i, element) in elements.iter().enumerate() {
if let Some(unresolved) = first_unresolved(element, &declared) {
refused.push((i, unresolved));
}
}
if refused.is_empty() {
return;
}
for (i, unresolved) in refused {
if let Element::Type(t) = &elements[i] {
declared.remove(&t.name().to_string());
}
let element = &mut elements[i];
let name = element.name().cloned();
let origin = Origin::new(
element.as_syn(),
Rc::clone(match element {
Element::Function(f) => &f.origin.location,
Element::Type(t) => t.location_rc(),
Element::Constant(c) => &c.origin.location,
Element::Guard(g) => &g.origin.location,
Element::Unsupported(u) => &u.origin.location,
}),
);
*element = Element::Unsupported(Unsupported {
name,
error: Box::new(ItemError::UnresolvedType { name: unresolved }),
origin,
});
}
}
}
fn element_type_refs(element: &Element) -> Vec<&TypeRef> {
let mut refs: Vec<&TypeRef> = Vec::new();
match element {
Element::Function(f) => {
refs.extend(f.params.iter().map(|p| &p.ty));
refs.push(&f.ret);
}
Element::Constant(c) => refs.push(&c.ty),
Element::Type(Type::Struct(s)) => refs.extend(s.fields.iter().map(|f| &f.ty)),
Element::Type(Type::Variant(v)) => refs.extend(
v.alternatives
.iter()
.flat_map(|a| a.fields.iter().map(|f| &f.ty)),
),
Element::Type(Type::Enum(_) | Type::Extern(_))
| Element::Guard(_)
| Element::Unsupported(_) => {}
}
refs
}
fn first_unresolved(
element: &Element,
declared: &std::collections::HashSet<String>,
) -> Option<String> {
element_type_refs(element)
.into_iter()
.find_map(|r| r.first_unresolved(declared))
}
pub fn extract_fn_trait_args(ty: &syn::Type) -> Option<Vec<syn::Type>> {
let syn::Type::ImplTrait(it) = ty else {
return None;
};
let mut args: Option<Vec<syn::Type>> = None;
let mut has_send = false;
let mut has_sync = false;
let mut has_static = false;
for bound in &it.bounds {
match bound {
syn::TypeParamBound::Trait(tb) => {
let last = tb.path.segments.last()?;
let name = last.ident.to_string();
match name.as_str() {
"Fn" => {
let syn::PathArguments::Parenthesized(p) = &last.arguments else {
return None;
};
match &p.output {
syn::ReturnType::Default => {}
syn::ReturnType::Type(_, t) if ty::is_unit_type(t) => {}
syn::ReturnType::Type(..) => return None,
}
args = Some(p.inputs.iter().cloned().collect());
}
"Send" => has_send = true,
"Sync" => has_sync = true,
_ => return None,
}
}
syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => has_static = true,
_ => return None,
}
}
if has_send && has_sync && has_static {
args
} else {
None
}
}
#[derive(Clone, Debug)]
pub enum ParseError {
DuplicateName(Box<DuplicateName>),
}
#[derive(Clone, Debug)]
pub struct DuplicateName {
pub name: syn::Ident,
pub first: SourceLocation,
pub second: SourceLocation,
pub first_crate: Option<String>,
pub second_crate: Option<String>,
}
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseError::DuplicateName(d) => {
let at = |loc: &SourceLocation, krate: &Option<String>| match krate {
Some(k) => format!("{loc} (crate `{k}`)"),
None => loc.to_string(),
};
write!(
f,
"duplicate `#[prebindgen]` name `{}`: first at {}, again at {} — marked items \
share one flat namespace across all source crates",
d.name,
at(&d.first, &d.first_crate),
at(&d.second, &d.second_crate)
)
}
}
}
}
impl std::error::Error for ParseError {}
#[derive(Clone, Debug)]
pub enum ItemError {
UnsupportedReceiver,
UnsupportedParamPattern { pattern: String },
ParamType {
param: syn::Ident,
source: UnsupportedType,
},
ReturnType { source: UnsupportedType },
FieldType {
field: syn::Ident,
source: UnsupportedType,
},
VariantFieldType {
variant: syn::Ident,
field: String,
source: UnsupportedType,
},
ConstType { source: UnsupportedType },
UnsupportedAsync,
UnsupportedVariadic,
UnsupportedGenericParam {
param: String,
kind: &'static str,
},
UnresolvedType { name: String },
UnsupportedItemKind { kind: &'static str },
}
impl fmt::Display for ItemError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ItemError::UnsupportedReceiver => write!(
f,
"takes a `self` receiver; `#[prebindgen]` captures free functions only"
),
ItemError::UnsupportedParamPattern { pattern } => write!(
f,
"parameter pattern `{pattern}` is not a plain name — bind each parameter to one \
identifier"
),
ItemError::ParamType { param, source } => {
write!(f, "parameter `{param}`: {source}")
}
ItemError::ReturnType { source } => write!(f, "return type: {source}"),
ItemError::FieldType { field, source } => write!(f, "field `{field}`: {source}"),
ItemError::VariantFieldType {
variant,
field,
source,
} => write!(f, "variant `{variant}` field `{field}`: {source}"),
ItemError::ConstType { source } => write!(f, "const type: {source}"),
ItemError::UnsupportedAsync => write!(
f,
"is an `async fn`; the boundary has no way to drive a future, and the generated \
wrapper would drop it and export a function whose body never runs — expose a \
blocking wrapper instead"
),
ItemError::UnsupportedVariadic => write!(
f,
"has a C-variadic tail, which the prebindgen source language does not model — \
take a slice, or one parameter per value"
),
ItemError::UnsupportedGenericParam { param, kind } => write!(
f,
"declares `{param}`, {kind}: the prebindgen source language has no generic \
binder, so an uninstantiated parameter is indistinguishable from a nominal type \
of the same name and no destination language can express it — write the \
concrete types, one marked item per instantiation (a newtype is the usual way)"
),
ItemError::UnresolvedType { name } if name.contains("::") => write!(
f,
"names the type `{name}`, which the flat API does not declare \u{2014} and being \
path-qualified it never could, because marked items live in one flat namespace \
of bare names. Give the type a name here with `#[prebindgen] pub type <Name> = \
{name};` and refer to that"
),
ItemError::UnresolvedType { name } => write!(
f,
"names the type `{name}`, which the flat API does not declare \u{2014} mark its \
declaration `#[prebindgen]`, or, for a foreign or crate-private type used as a \
handle, give it a name here with `#[prebindgen] pub type {name} = ..;`"
),
ItemError::UnsupportedItemKind { kind } => write!(
f,
"is {kind}; the prebindgen source language models functions, structs, enums and \
consts — everything else belongs in the consumer crate"
),
}
}
}
impl std::error::Error for ItemError {}
fn lower_item(item: syn::Item, loc: SourceLocation, consts: &ConstIndex) -> Element {
let at = Rc::new(loc);
match item {
syn::Item::Fn(f) => match lower_fn(&f, &at, consts) {
Ok(func) => Element::Function(func),
Err(error) => unsupported(f.sig.ident.clone(), syn::Item::Fn(f), &at, error),
},
syn::Item::Struct(s) => match lower_struct(&s, &at, consts) {
Ok(ty) => Element::Type(ty),
Err(error) => unsupported(s.ident.clone(), syn::Item::Struct(s), &at, error),
},
syn::Item::Enum(e) => match lower_enum(&e, &at, consts) {
Ok(ty) => Element::Type(ty),
Err(error) => unsupported(e.ident.clone(), syn::Item::Enum(e), &at, error),
},
syn::Item::Type(t) => match reject_generic_params(&t.generics) {
Err(error) => unsupported(t.ident.clone(), syn::Item::Type(t), &at, error),
Ok(()) => {
let target = Some(t.ty.to_token_stream().to_string());
Element::Type(Type::Extern(Extern {
name: t.ident.clone(),
target,
origin: Origin::new(syn::Item::Type(t), at),
}))
}
},
syn::Item::Const(c) if c.ident == "_" => Element::Guard(Guard {
origin: Origin::new(c, at),
}),
syn::Item::Const(c) => match lower_type(&c.ty, consts, &at) {
Ok(ty) => Element::Constant(Constant {
name: c.ident.clone(),
ty,
origin: Origin::new(c, at),
}),
Err(source) => unsupported(
c.ident.clone(),
syn::Item::Const(c),
&at,
ItemError::ConstType { source },
),
},
other => {
let (name, kind) = match &other {
syn::Item::Union(u) => (Some(u.ident.clone()), "a union"),
_ => (None, "an item kind"),
};
unsupported(name, other, &at, ItemError::UnsupportedItemKind { kind })
}
}
}
fn unsupported(
name: impl Into<Option<syn::Ident>>,
syntax: syn::Item,
at: &Rc<SourceLocation>,
error: ItemError,
) -> Element {
Element::Unsupported(Unsupported {
name: name.into(),
error: Box::new(error),
origin: Origin::new(syntax, Rc::clone(at)),
})
}
fn reject_generic_params(generics: &syn::Generics) -> Result<(), ItemError> {
for param in &generics.params {
let (name, kind) = match param {
syn::GenericParam::Lifetime(_) => continue,
syn::GenericParam::Type(t) => (t.ident.to_string(), "a type parameter"),
syn::GenericParam::Const(c) => (c.ident.to_string(), "a const generic parameter"),
};
return Err(ItemError::UnsupportedGenericParam { param: name, kind });
}
Ok(())
}
fn lower_fn(
f: &syn::ItemFn,
at: &Rc<SourceLocation>,
consts: &ConstIndex,
) -> Result<Function, ItemError> {
if f.sig.asyncness.is_some() {
return Err(ItemError::UnsupportedAsync);
}
if f.sig.variadic.is_some() {
return Err(ItemError::UnsupportedVariadic);
}
reject_generic_params(&f.sig.generics)?;
let mut params = Vec::with_capacity(f.sig.inputs.len());
for input in &f.sig.inputs {
let pt = match input {
syn::FnArg::Receiver(_) => return Err(ItemError::UnsupportedReceiver),
syn::FnArg::Typed(pt) => pt,
};
let syn::Pat::Ident(pat) = &*pt.pat else {
return Err(ItemError::UnsupportedParamPattern {
pattern: pt.pat.to_token_stream().to_string(),
});
};
let name = pat.ident.clone();
let ty = lower_type(&pt.ty, consts, at).map_err(|source| ItemError::ParamType {
param: name.clone(),
source,
})?;
params.push(Param {
name,
ty,
origin: Origin::new(pt.clone(), Rc::clone(at)),
});
}
let ret = match &f.sig.output {
syn::ReturnType::Default => TypeRef {
kind: TypeKind::Unit,
origin: Origin::new(syn::parse_quote!(()), Rc::clone(at)),
},
syn::ReturnType::Type(_, t) => {
lower_type(t, consts, at).map_err(|source| ItemError::ReturnType { source })?
}
};
Ok(Function {
name: f.sig.ident.clone(),
params,
ret,
origin: Origin::new(f.clone(), Rc::clone(at)),
})
}
fn lower_struct(
s: &syn::ItemStruct,
at: &Rc<SourceLocation>,
consts: &ConstIndex,
) -> Result<Type, ItemError> {
reject_generic_params(&s.generics)?;
let fields = match &s.fields {
syn::Fields::Named(named) => {
let mut out = Vec::with_capacity(named.named.len());
for (index, f) in named.named.iter().enumerate() {
let name = f.ident.clone().expect("named fields have idents");
let ty = lower_type(&f.ty, consts, at).map_err(|source| ItemError::FieldType {
field: name.clone(),
source,
})?;
out.push(Field {
name: Some(name),
index,
ty,
origin: Origin::new(f.clone(), Rc::clone(at)),
});
}
out
}
syn::Fields::Unnamed(_) => {
return Ok(Type::Extern(Extern {
name: s.ident.clone(),
target: None,
origin: Origin::new(syn::Item::Struct(s.clone()), Rc::clone(at)),
}));
}
syn::Fields::Unit => Vec::new(),
};
Ok(Type::Struct(Struct {
reading: TypeRef::named(&s.ident),
name: s.ident.clone(),
fields,
origin: Origin::new(s.clone(), Rc::clone(at)),
}))
}
fn lower_enum(
e: &syn::ItemEnum,
at: &Rc<SourceLocation>,
consts: &ConstIndex,
) -> Result<Type, ItemError> {
reject_generic_params(&e.generics)?;
if e.variants.iter().any(|v| !v.fields.is_empty()) {
return Ok(Type::Variant(lower_variant(e, at, consts)?));
}
Ok(Type::Enum(lower_c_enum(e, at)))
}
fn lower_variant(
e: &syn::ItemEnum,
at: &Rc<SourceLocation>,
consts: &ConstIndex,
) -> Result<Variant, ItemError> {
let mut alternatives = Vec::with_capacity(e.variants.len());
for (index, v) in e.variants.iter().enumerate() {
let mut fields = Vec::with_capacity(v.fields.len());
for (field_index, f) in v.fields.iter().enumerate() {
let ty =
lower_type(&f.ty, consts, at).map_err(|source| ItemError::VariantFieldType {
variant: v.ident.clone(),
field: match &f.ident {
Some(id) => id.to_string(),
None => field_index.to_string(),
},
source,
})?;
fields.push(Field {
name: f.ident.clone(),
index: field_index,
ty,
origin: Origin::new(f.clone(), Rc::clone(at)),
});
}
alternatives.push(Alternative {
name: v.ident.clone(),
index,
fields,
origin: Origin::new(v.clone(), Rc::clone(at)),
});
}
Ok(Variant {
reading: TypeRef::named(&e.ident),
name: e.ident.clone(),
alternatives,
origin: Origin::new(e.clone(), Rc::clone(at)),
})
}
fn lower_c_enum(e: &syn::ItemEnum, at: &Rc<SourceLocation>) -> Enum {
let mut values = Vec::with_capacity(e.variants.len());
let mut next: Option<i64> = Some(0);
for (index, v) in e.variants.iter().enumerate() {
let discriminant = match v.discriminant.as_ref() {
Some((_, expr)) => int_literal(expr),
None => next,
};
next = discriminant.and_then(|n| n.checked_add(1));
values.push(EnumValue {
name: v.ident.clone(),
index,
discriminant,
origin: Origin::new(v.clone(), Rc::clone(at)),
});
}
Enum {
reading: TypeRef::named(&e.ident),
name: e.ident.clone(),
values,
origin: Origin::new(e.clone(), Rc::clone(at)),
}
}
fn int_literal(expr: &syn::Expr) -> Option<i64> {
i64::try_from(int_literal_wide(expr)?).ok()
}
fn int_literal_wide(expr: &syn::Expr) -> Option<i128> {
match expr {
syn::Expr::Lit(lit) => match &lit.lit {
syn::Lit::Int(int) => int.base10_parse::<i128>().ok(),
_ => None,
},
syn::Expr::Unary(syn::ExprUnary {
op: syn::UnOp::Neg(_),
expr,
..
}) => int_literal_wide(expr).map(|v| -v),
_ => None,
}
}