use std::{collections::HashMap, fmt, rc::Rc};
use prebindgen::SourceLocation;
use quote::ToTokens;
use super::origin::Origin;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnsupportedArrayLen {
pub array: String,
pub offending: String,
pub reason: ArrayLenReason,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ArrayLenReason {
NotLiteralOrName,
NotAnIntegerLiteral,
IntegerOutOfRange,
NotABareName,
NotAMarkedConst,
ConstIsNotALiteral,
ForeignSourceConst {
const_crate: String,
item_crate: String,
},
}
impl fmt::Display for UnsupportedArrayLen {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let what = match &self.reason {
ArrayLenReason::NotLiteralOrName => {
"is neither an integer literal nor the name of a const".to_string()
}
ArrayLenReason::NotAnIntegerLiteral => {
"is not a non-negative integer literal".to_string()
}
ArrayLenReason::IntegerOutOfRange => "does not fit in a `usize`".to_string(),
ArrayLenReason::NotABareName => {
"is a path rather than a bare name; `#[prebindgen]` items live in one flat \
namespace, so the bare name is the whole address"
.to_string()
}
ArrayLenReason::NotAMarkedConst => {
"names no `#[prebindgen]` const — the generated crate sees only what the macro \
exposed, so mark it `#[prebindgen]`"
.to_string()
}
ArrayLenReason::ConstIsNotALiteral => {
"names a const whose value is not an integer literal, so `build.rs` cannot \
evaluate it"
.to_string()
}
ArrayLenReason::ForeignSourceConst {
const_crate,
item_crate,
} => format!(
"names a const marked in `{const_crate}`, but the item using it comes from \
`{item_crate}` — a length must name a const from its own source crate"
),
};
write!(
f,
"fixed-size array `{}`: the length `{}` {what}. A length must be an integer literal, \
or the bare name of a `#[prebindgen]` const that is itself an integer literal \
(`pub const N: usize = 4;`) — a generator runs in `build.rs` and cannot evaluate \
anything else, and some destination languages need the count as a number.",
self.array, self.offending
)
}
}
impl std::error::Error for UnsupportedArrayLen {}
#[derive(Clone, Debug)]
pub struct ArrayExtent {
pub value: usize,
pub source: ExtentSource,
pub origin: Origin<syn::Expr>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ExtentSource {
Literal,
Const(ConstId),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConstId {
pub name: String,
pub crate_name: Option<String>,
}
impl ArrayExtent {
pub fn const_id(&self) -> Option<&ConstId> {
match &self.source {
ExtentSource::Literal => None,
ExtentSource::Const(id) => Some(id),
}
}
}
struct ConstEntry {
value: Option<usize>,
crate_name: Option<String>,
}
pub(crate) struct ConstIndex {
consts: HashMap<String, ConstEntry>,
}
impl ConstIndex {
pub(crate) fn new<I>(consts: I) -> Self
where
I: IntoIterator<Item = (String, syn::Expr, Option<String>)>,
{
Self {
consts: consts
.into_iter()
.map(|(name, expr, crate_name)| {
let entry = ConstEntry {
value: int_literal(&expr),
crate_name,
};
(name, entry)
})
.collect(),
}
}
}
fn int_literal(expr: &syn::Expr) -> Option<usize> {
let syn::Expr::Lit(lit) = expr else {
return None;
};
let syn::Lit::Int(int) = &lit.lit else {
return None;
};
int.base10_parse::<usize>().ok()
}
pub(crate) fn lower_array_len(
len: &syn::Expr,
array: &str,
at: &Rc<SourceLocation>,
consts: &ConstIndex,
) -> Result<ArrayExtent, UnsupportedArrayLen> {
let item_crate = at.crate_name.as_deref();
let origin = || Origin::new(len.clone(), Rc::clone(at));
let fail = |reason| UnsupportedArrayLen {
array: array.to_string(),
offending: len.to_token_stream().to_string(),
reason,
};
match len {
syn::Expr::Lit(_) => match int_literal(len) {
Some(value) => Ok(ArrayExtent {
value,
source: ExtentSource::Literal,
origin: origin(),
}),
None => Err(fail(match len {
syn::Expr::Lit(l) if matches!(l.lit, syn::Lit::Int(_)) => {
ArrayLenReason::IntegerOutOfRange
}
_ => ArrayLenReason::NotAnIntegerLiteral,
})),
},
syn::Expr::Path(ep) => {
if ep.qself.is_some() || ep.path.leading_colon.is_some() || ep.path.segments.len() != 1
{
return Err(fail(ArrayLenReason::NotABareName));
}
let name = ep.path.segments[0].ident.to_string();
let Some(entry) = consts.consts.get(&name) else {
return Err(fail(ArrayLenReason::NotAMarkedConst));
};
if entry.crate_name.as_deref() != item_crate {
return Err(fail(ArrayLenReason::ForeignSourceConst {
const_crate: entry
.crate_name
.clone()
.unwrap_or_else(|| "<unstamped>".into()),
item_crate: item_crate.unwrap_or("<unstamped>").to_string(),
}));
}
let Some(value) = entry.value else {
return Err(fail(ArrayLenReason::ConstIsNotALiteral));
};
Ok(ArrayExtent {
value,
source: ExtentSource::Const(ConstId {
name,
crate_name: entry.crate_name.clone(),
}),
origin: origin(),
})
}
_ => Err(fail(ArrayLenReason::NotLiteralOrName)),
}
}