use std::collections::{HashMap, HashSet};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::{format_ident, quote};
use ridl_ir::name::{camel_case, snake_case};
use ridl_ir::projection::flatbuffers as fb_projection;
use ridl_ir::v2;
use crate::{
Ctx, GenerateError, InducedTuple, ScalarBacking, backing_scalar, check_flatbuffers_bound,
field_type_tokens, ident, type_path, unjudgeable_members, vis_tokens,
};
const BUFFER_ALIGN: usize = 8;
pub(crate) fn package_items(
ctx: &Ctx,
package: &v2::Package,
tuples: &[InducedTuple],
) -> Result<Vec<TokenStream>, GenerateError> {
Codec {
ctx,
package,
tuples: tuples
.iter()
.map(|induced| (induced.name.clone(), induced))
.collect(),
}
.items()
}
struct Codec<'a> {
ctx: &'a Ctx<'a>,
package: &'a v2::Package,
tuples: HashMap<String, &'a InducedTuple>,
}
#[derive(Debug, Clone)]
enum Wire {
Scalar(Scalar),
Text(Option<NamedScalar>),
Bytes(Option<NamedScalar>),
Table(String),
Union(String),
Vector {
element: Box<Wire>,
min: u64,
max: u64,
},
Map {
entry: Box<Table>,
min: u64,
max: u64,
},
}
impl Wire {
fn inline_width(&self) -> usize {
match self {
Wire::Scalar(scalar) => scalar.prim.width(),
_ => 4,
}
}
}
#[derive(Debug, Clone)]
struct NamedScalar {
name: String,
ctor: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Prim {
Bool,
I8,
U8,
I16,
U16,
I32,
U32,
I64,
U64,
F32,
F64,
}
impl Prim {
fn width(self) -> usize {
match self {
Prim::Bool | Prim::I8 | Prim::U8 => 1,
Prim::I16 | Prim::U16 => 2,
Prim::I32 | Prim::U32 | Prim::F32 => 4,
Prim::I64 | Prim::U64 | Prim::F64 => 8,
}
}
fn field_variant(self) -> &'static str {
match self {
Prim::Bool => "Bool",
Prim::I8 => "I8",
Prim::U8 => "U8",
Prim::I16 => "I16",
Prim::U16 => "U16",
Prim::I32 => "I32",
Prim::U32 => "U32",
Prim::I64 => "I64",
Prim::U64 => "U64",
Prim::F32 => "F32",
Prim::F64 => "F64",
}
}
fn read_fn(self) -> &'static str {
match self {
Prim::Bool => "read_bool",
Prim::I8 => "read_i8",
Prim::U8 => "read_u8",
Prim::I16 => "read_i16",
Prim::U16 => "read_u16",
Prim::I32 => "read_i32",
Prim::U32 => "read_u32",
Prim::I64 => "read_i64",
Prim::U64 => "read_u64",
Prim::F32 => "read_f32",
Prim::F64 => "read_f64",
}
}
fn rust_name(self) -> &'static str {
match self {
Prim::Bool => "bool",
Prim::I8 => "i8",
Prim::U8 => "u8",
Prim::I16 => "i16",
Prim::U16 => "u16",
Prim::I32 => "i32",
Prim::U32 => "u32",
Prim::I64 => "i64",
Prim::U64 => "u64",
Prim::F32 => "f32",
Prim::F64 => "f64",
}
}
fn neutral(self) -> TokenStream {
match self {
Prim::Bool => quote! { false },
Prim::F32 => quote! { 0.0f32 },
Prim::F64 => quote! { 0.0f64 },
other => {
let literal: TokenStream = format!("0{}", other.rust_name())
.parse()
.unwrap_or_else(|_| quote! { 0 });
literal
}
}
}
}
#[derive(Debug, Clone)]
enum Repr {
Bool,
Int,
Float,
Named(NamedScalar),
Enum { name: String, first: String },
EnumSet { name: String },
}
#[derive(Debug, Clone)]
struct Scalar {
prim: Prim,
repr: Repr,
}
impl Scalar {
fn raw(&self, expr: TokenStream) -> TokenStream {
let base = match &self.repr {
Repr::Bool | Repr::Int | Repr::Float => quote! { #expr },
Repr::Named(_) => quote! { #expr.get() },
Repr::Enum { .. } | Repr::EnumSet { .. } => quote! { i64::from(#expr) },
};
match self.prim {
Prim::Bool | Prim::I64 | Prim::F64 => base,
other => {
let ty = format_ident!("{}", other.rust_name());
quote! { #base as #ty }
}
}
}
fn read(&self, buf: &TokenStream, at: &TokenStream) -> TokenStream {
let call = format_ident!("{}", self.prim.read_fn());
quote! { ::ridl_rt::flatbuffers::#call(#buf, #at) }
}
fn widen(&self, raw: TokenStream) -> TokenStream {
match self.prim {
Prim::Bool | Prim::I64 | Prim::F64 => raw,
Prim::U64 => quote! { #raw as i64 },
Prim::F32 => quote! { f64::from(#raw) },
_ => quote! { i64::from(#raw) },
}
}
fn decode(&self, buf: &TokenStream, at: &TokenStream) -> TokenStream {
let read = self.read(buf, at);
let neutral = self.prim.neutral();
let widened = self.widen(quote! { #read.unwrap_or(#neutral) });
match &self.repr {
Repr::Bool | Repr::Int | Repr::Float => widened,
Repr::Named(named) => {
let ty = type_path(&named.name);
let ctor = format_ident!("{}", named.ctor);
quote! { #ty::#ctor(#widened) }
}
Repr::Enum { name, first } => {
let ty = type_path(name);
let variant = ident(first);
quote! {
<#ty as ::core::convert::TryFrom<i64>>::try_from(#widened)
.unwrap_or(#ty::#variant)
}
}
Repr::EnumSet { name } => {
let ty = type_path(name);
quote! {
<#ty as ::core::convert::TryFrom<i64>>::try_from(#widened)
.unwrap_or(#ty(0i64))
}
}
}
}
}
#[derive(Clone)]
struct Operand {
place: TokenStream,
reference: TokenStream,
}
impl Operand {
fn owned(place: TokenStream) -> Self {
Operand {
reference: quote! { &#place },
place,
}
}
fn borrowed(reference: TokenStream) -> Self {
Operand {
place: quote! { *#reference },
reference,
}
}
}
#[derive(Debug, Clone)]
enum Access {
Field(String),
Position(usize),
}
#[derive(Debug, Clone)]
struct Slot {
id: u16,
offset: u16,
wire: Wire,
optional: bool,
name: String,
field_type: v2::FieldType,
hint: String,
access: Access,
}
impl Slot {
fn operand(&self, base: &TokenStream) -> Operand {
match &self.access {
Access::Field(name) => {
let field = ident(&snake_case(name));
Operand::owned(quote! { #base.#field })
}
Access::Position(index) => {
let position = Literal::usize_unsuffixed(*index);
Operand::owned(quote! { #base.#position })
}
}
}
}
struct Entry {
id: u16,
name: String,
field_type: v2::FieldType,
hint: String,
wire: Wire,
}
#[derive(Debug, Clone)]
struct Table {
slots: Vec<Slot>,
size: usize,
align: usize,
vtable_slots: u16,
}
fn place(widths: &[usize]) -> (Vec<u16>, usize, usize) {
let mut cursor = 4usize;
let mut align = 4usize;
let mut offsets = Vec::with_capacity(widths.len());
for width in widths {
let width = *width;
cursor = cursor.div_ceil(width) * width;
offsets.push(cursor as u16);
cursor += width;
align = align.max(width);
}
(offsets, cursor.max(4), align)
}
fn field_hint(parent: &str, field: &str) -> String {
format!("{}{}", camel_case(parent), camel_case(field))
}
fn view_ident(owner: &str) -> Ident {
format_ident!("{}FbView", ident(owner))
}
fn encode_ident(owner: &str) -> Ident {
format_ident!("__ridl_fb_encode_{}", snake_case(owner))
}
fn verify_ident(owner: &str) -> Ident {
format_ident!("__ridl_fb_verify_{}", snake_case(owner))
}
fn decode_ident(owner: &str) -> Ident {
format_ident!("__ridl_fb_decode_{}", snake_case(owner))
}
fn split_owner(owner: &str) -> (Option<&str>, &str) {
match owner.rsplit_once('.') {
Some((package, name)) => (Some(package), name),
None => (None, owner),
}
}
fn owner_prefix(package: Option<&str>) -> TokenStream {
match package {
Some(package) => {
let segments = package.split('.').map(ident);
quote! { crate #(:: #segments)* :: }
}
None => quote! {},
}
}
fn view_path(owner: &str) -> TokenStream {
let (package, name) = split_owner(owner);
let prefix = owner_prefix(package);
let id = view_ident(name);
quote! { #prefix #id }
}
fn encode_path(owner: &str) -> TokenStream {
let (package, name) = split_owner(owner);
let prefix = owner_prefix(package);
let id = encode_ident(name);
quote! { #prefix #id }
}
fn verify_path(owner: &str) -> TokenStream {
let (package, name) = split_owner(owner);
let prefix = owner_prefix(package);
let id = verify_ident(name);
quote! { #prefix #id }
}
fn decode_path(owner: &str) -> TokenStream {
let (package, name) = split_owner(owner);
let prefix = owner_prefix(package);
let id = decode_ident(name);
quote! { #prefix #id }
}
fn named_scalar_check(named: &NamedScalar, value: TokenStream) -> Option<TokenStream> {
if named.ctor != "new_unchecked" {
return None;
}
let ty = type_path(&named.name);
Some(quote! {
#ty::check(#value)
.map_err(::ridl_rt::payload::VerifyError::Contract)?;
})
}
impl<'a> Codec<'a> {
fn resolve(&self, reference: &str) -> Option<(&'a v2::Decl, String)> {
if let Some(decl) = self.ctx.lookup(reference) {
return Some((decl, decl.name.clone()));
}
let (package_name, name) = reference.rsplit_once('.')?;
let package = self
.ctx
.others
.iter()
.find(|other| other.name == package_name)?;
let decl = package.decls.iter().find(|decl| decl.name == name)?;
Some((decl, reference.to_string()))
}
fn packages(&self) -> fb_projection::Packages<'_> {
fb_projection::Packages {
package: self.package,
others: self.ctx.others,
}
}
fn items(&self) -> Result<Vec<TokenStream>, GenerateError> {
let mut roots: Vec<&v2::Decl> = Vec::new();
let mut withheld: Vec<&v2::Decl> = Vec::new();
for decl in &self.package.decls {
if fb_projection::root_table(decl).is_none() {
continue;
}
match fb_projection::max_size(self.packages(), decl) {
Some(_) => roots.push(decl),
None => {
check_flatbuffers_bound(self.ctx, self.package, decl)?;
withheld.push(decl);
}
}
}
let mut items: Vec<TokenStream> = Vec::new();
for decl in &withheld {
items.push(self.withheld_note(decl));
}
for decl in &roots {
items.extend(self.decl_items(decl)?);
}
for name in self.reachable_tuples(&roots) {
let induced = self.tuples[&name];
let table = self.tuple_table(induced)?;
items.extend(self.table_items(&induced.name, induced.visibility, &table)?);
}
Ok(items)
}
fn withheld_note(&self, decl: &v2::Decl) -> TokenStream {
let name = format_ident!(
"__RIDL_FB_NO_CODEC_{}",
snake_case(&decl.name).to_uppercase()
);
let members = unjudgeable_members(self.ctx, decl);
let causes = if self.ctx.others.is_empty() {
"a reference into another package, which this call was handed no \
package to resolve, a same-package cycle, or a stream"
} else {
"a same-package cycle, or a stream"
};
let cause = if members.is_empty() {
" No member of it could be judged.".to_string()
} else {
format!(
" The member{} {} reach{} a reference this backend does not resolve — {causes}.",
if members.len() == 1 { "" } else { "s" },
members
.iter()
.map(|member| format!("`{member}`"))
.collect::<Vec<_>>()
.join(", "),
if members.len() == 1 { "es" } else { "" },
)
};
let headline = format!(
" `{}` carries no `Payload<FlatBuffers>` implementation.",
decl.name
);
quote! {
#[doc = #headline]
#[doc = #cause]
#[allow(dead_code)]
const #name: () = ();
}
}
fn reachable_tuples(&self, roots: &[&v2::Decl]) -> Vec<String> {
let mut found: Vec<String> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
let mut queue: Vec<(String, v2::FieldType)> = Vec::new();
for decl in roots {
if let Some(v2::decl::Kind::StructDef(def)) = &decl.kind {
for member in &def.members {
if let Some(v2::struct_member::Member::Field(field)) = &member.member
&& let Some(ty) = field.r#type.clone()
{
queue.push((field_hint(&decl.name, &field.name), ty));
}
}
}
}
let mut index = 0;
while index < queue.len() {
let (hint, ty) = queue[index].clone();
index += 1;
match ty.kind {
Some(v2::field_type::Kind::Tuple(tuple)) => {
if self.tuples.contains_key(&hint) && seen.insert(hint.clone()) {
found.push(hint.clone());
for field in &tuple.fields {
if let Some(inner) = field.r#type.clone() {
queue.push((format!("{hint}{}", camel_case(&field.name)), inner));
}
}
}
}
Some(v2::field_type::Kind::Array(array)) => {
if let Some(element) = array.element {
queue.push((format!("{hint}Element"), *element));
}
}
Some(v2::field_type::Kind::Map(map)) => {
if let Some(key) = map.key {
queue.push((format!("{hint}Key"), *key));
}
if let Some(value) = map.value {
queue.push((format!("{hint}Value"), *value));
}
}
_ => {}
}
}
found
}
fn decl_items(&self, decl: &v2::Decl) -> Result<Vec<TokenStream>, GenerateError> {
match &decl.kind {
Some(v2::decl::Kind::StructDef(def)) => {
let table = self.struct_table(&decl.name, def)?;
let mut items = self.table_items(&decl.name, decl.visibility, &table)?;
items.push(self.payload_impl(decl)?);
Ok(items)
}
Some(v2::decl::Kind::UnionDef(def)) => {
let mut items = self.union_items(decl, def)?;
items.push(self.payload_impl(decl)?);
Ok(items)
}
Some(
v2::decl::Kind::TypeDef(_)
| v2::decl::Kind::EnumDef(_)
| v2::decl::Kind::EnumSetDef(_),
) => {
let mut items = self.root_box_items(decl)?;
items.push(self.payload_impl(decl)?);
Ok(items)
}
_ => Ok(Vec::new()),
}
}
fn wire(&self, ty: &v2::FieldType, hint: &str) -> Result<Wire, GenerateError> {
match ty.kind.as_ref() {
Some(v2::field_type::Kind::Primitive(primitive)) => {
match v2::PrimitiveType::try_from(*primitive).ok() {
Some(v2::PrimitiveType::Boolean) => Ok(Wire::Scalar(Scalar {
prim: Prim::Bool,
repr: Repr::Bool,
})),
Some(v2::PrimitiveType::Integer) => Ok(Wire::Scalar(Scalar {
prim: Prim::I64,
repr: Repr::Int,
})),
Some(v2::PrimitiveType::Float) => Ok(Wire::Scalar(Scalar {
prim: Prim::F64,
repr: Repr::Float,
})),
_ => Err(GenerateError {
message: "a FlatBuffers codec cannot carry an unbounded primitive"
.to_string(),
}),
}
}
Some(v2::field_type::Kind::InlineScalar(td)) => self.scalar_wire(td, None),
Some(v2::field_type::Kind::Named(reference)) => {
let Some((decl, owner)) = self.resolve(reference) else {
return Err(GenerateError {
message: format!(
"`{reference}` resolves in no package of this build, so no \
FlatBuffers codec can be emitted for it"
),
});
};
match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => self.scalar_wire(
td,
Some(NamedScalar {
name: owner.clone(),
ctor: if v2::constraint_is_vacuous(td.constraint.as_ref()) {
"new"
} else {
"new_unchecked"
},
}),
),
Some(v2::decl::Kind::EnumDef(def)) => {
let Some(first) = def.values.first() else {
return Err(GenerateError {
message: format!(
"`{}` declares no value, so no FlatBuffers codec can decode \
one",
decl.name
),
});
};
Ok(Wire::Scalar(Scalar {
prim: Prim::I64,
repr: Repr::Enum {
name: owner.clone(),
first: first.name.clone(),
},
}))
}
Some(v2::decl::Kind::EnumSetDef(def)) => Ok(Wire::Scalar(Scalar {
prim: int_prim(def.width).ok_or_else(|| GenerateError {
message: format!("`{}` carries no integer width", decl.name),
})?,
repr: Repr::EnumSet {
name: owner.clone(),
},
})),
Some(v2::decl::Kind::StructDef(_)) => Ok(Wire::Table(owner.clone())),
Some(v2::decl::Kind::UnionDef(_)) => Ok(Wire::Union(owner.clone())),
_ => Err(GenerateError {
message: format!(
"`{reference}` names a declaration a FlatBuffers codec cannot carry"
),
}),
}
}
Some(v2::field_type::Kind::Array(array)) => {
let element = array.element.as_deref().ok_or_else(|| GenerateError {
message: "an array carries no element type".to_string(),
})?;
self.refuse_optional(element, "an array element")?;
Ok(Wire::Vector {
element: Box::new(self.wire(element, &format!("{hint}Element"))?),
min: array.min,
max: array.max,
})
}
Some(v2::field_type::Kind::Map(map)) => {
let key = map.key.as_deref().ok_or_else(|| GenerateError {
message: "a map carries no key type".to_string(),
})?;
let value = map.value.as_deref().ok_or_else(|| GenerateError {
message: "a map carries no value type".to_string(),
})?;
self.refuse_optional(key, "a map key")?;
self.refuse_optional(value, "a map value")?;
let key_hint = format!("{hint}Key");
let value_hint = format!("{hint}Value");
let entry = self.map_entry_table(
self.wire(key, &key_hint)?,
key.clone(),
key_hint,
self.wire(value, &value_hint)?,
value.clone(),
value_hint,
);
Ok(Wire::Map {
entry: Box::new(entry),
min: map.min,
max: map.max,
})
}
Some(v2::field_type::Kind::Tuple(_)) => Ok(Wire::Table(hint.to_string())),
_ => Err(GenerateError {
message: "a FlatBuffers codec cannot carry this type position".to_string(),
}),
}
}
fn refuse_optional(&self, ty: &v2::FieldType, what: &str) -> Result<(), GenerateError> {
if ty.optional {
return Err(GenerateError {
message: format!(
"{what} is optional, which FlatBuffers cannot represent — only a table field \
may be absent"
),
});
}
Ok(())
}
fn scalar_wire(
&self,
td: &v2::TypeDef,
named: Option<NamedScalar>,
) -> Result<Wire, GenerateError> {
let backing = backing_scalar(td);
match &td.width {
Some(v2::type_def::Width::IntWidth(width)) => {
let prim = int_prim(*width).ok_or_else(|| GenerateError {
message: "a scalar carries no integer width".to_string(),
})?;
Ok(Wire::Scalar(Scalar {
prim,
repr: scalar_repr(named, backing),
}))
}
Some(v2::type_def::Width::FloatWidth(width)) => {
let prim = match v2::FloatWidth::try_from(*width).ok() {
Some(v2::FloatWidth::F32) => Prim::F32,
Some(v2::FloatWidth::F64) => Prim::F64,
_ => {
return Err(GenerateError {
message: "a scalar carries no float width".to_string(),
});
}
};
Ok(Wire::Scalar(Scalar {
prim,
repr: scalar_repr(named, backing),
}))
}
None => match backing {
ScalarBacking::Boolean => Ok(Wire::Scalar(Scalar {
prim: Prim::Bool,
repr: scalar_repr(named, backing),
})),
ScalarBacking::String => Ok(Wire::Text(named)),
ScalarBacking::Bytes => Ok(Wire::Bytes(named)),
ScalarBacking::Float | ScalarBacking::Integer => Err(GenerateError {
message: "a numeric scalar carries no width".to_string(),
}),
},
}
}
fn struct_table(&self, owner: &str, def: &v2::StructDef) -> Result<Table, GenerateError> {
let layout = fb_projection::struct_table(owner, def).map_err(|err| GenerateError {
message: err.message,
})?;
let mut entries: Vec<Entry> = Vec::new();
for member in &def.members {
let Some(v2::struct_member::Member::Field(field)) = &member.member else {
continue;
};
let Some(ty) = field.r#type.as_ref() else {
return Err(GenerateError {
message: format!("`{owner}.{}` carries no type", field.name),
});
};
let id = u16::try_from(field.ordinal.saturating_sub(1)).map_err(|_| GenerateError {
message: format!(
"`{owner}.{}` has ordinal {}, which a FlatBuffers vtable cannot carry",
field.name, field.ordinal
),
})?;
let hint = field_hint(owner, &field.name);
let wire = self.wire(ty, &hint)?;
entries.push(Entry {
id,
name: field.name.clone(),
field_type: ty.clone(),
hint,
wire,
});
}
self.assemble(owner, layout.vtable_slots(), entries)
}
fn tuple_table(&self, induced: &InducedTuple) -> Result<Table, GenerateError> {
let layout = fb_projection::tuple_table(&induced.name, &induced.tuple).map_err(|err| {
GenerateError {
message: err.message,
}
})?;
let mut entries: Vec<Entry> = Vec::new();
for (index, field) in induced.tuple.fields.iter().enumerate() {
let Some(ty) = field.r#type.as_ref() else {
return Err(GenerateError {
message: format!("`{}.{}` carries no type", induced.name, field.name),
});
};
let id = u16::try_from(index).map_err(|_| GenerateError {
message: format!(
"`{}` has more tuple fields than a FlatBuffers vtable can carry",
induced.name
),
})?;
let hint = format!("{}{}", induced.name, camel_case(&field.name));
let wire = self.wire(ty, &hint)?;
entries.push(Entry {
id,
name: field.name.clone(),
field_type: ty.clone(),
hint,
wire,
});
}
self.assemble(&induced.name, layout.vtable_slots(), entries)
}
#[allow(clippy::too_many_arguments)]
fn map_entry_table(
&self,
key: Wire,
key_type: v2::FieldType,
key_hint: String,
value: Wire,
value_type: v2::FieldType,
value_hint: String,
) -> Table {
let widths = vec![key.inline_width(), value.inline_width()];
let (offsets, size, align) = place(&widths);
Table {
slots: vec![
Slot {
id: fb_projection::MAP_ENTRY_KEY_ID as u16,
offset: offsets[0],
wire: key,
optional: false,
name: "key".to_string(),
field_type: key_type,
hint: key_hint,
access: Access::Position(0),
},
Slot {
id: fb_projection::MAP_ENTRY_VALUE_ID as u16,
offset: offsets[1],
wire: value,
optional: false,
name: "value".to_string(),
field_type: value_type,
hint: value_hint,
access: Access::Position(1),
},
],
size,
align,
vtable_slots: 2,
}
}
fn assemble(
&self,
owner: &str,
vtable_slots: u64,
entries: Vec<Entry>,
) -> Result<Table, GenerateError> {
let widths: Vec<usize> = entries
.iter()
.map(|entry| entry.wire.inline_width())
.collect();
let (offsets, size, align) = place(&widths);
if size > usize::from(u16::MAX) {
return Err(GenerateError {
message: format!(
"`{owner}` needs a {size}-byte FlatBuffers table, and a vtable states a \
table's size as a u16"
),
});
}
let vtable_slots = u16::try_from(vtable_slots).map_err(|_| GenerateError {
message: format!("`{owner}` needs more vtable slots than a FlatBuffers table carries"),
})?;
let slots = entries
.into_iter()
.zip(offsets)
.map(|(entry, offset)| Slot {
id: entry.id,
offset,
optional: entry.field_type.optional,
access: Access::Field(entry.name.clone()),
name: entry.name,
field_type: entry.field_type,
hint: entry.hint,
wire: entry.wire,
})
.collect();
Ok(Table {
slots,
size,
align,
vtable_slots,
})
}
fn table_items(
&self,
owner: &str,
visibility: i32,
table: &Table,
) -> Result<Vec<TokenStream>, GenerateError> {
let vis = vis_tokens(visibility);
let ty = ident(owner);
let view = view_ident(owner);
let doc =
format!(" A zero-copy accessor over FlatBuffers bytes `{owner}`'s `verify` accepted.");
let mut accessors: Vec<TokenStream> = Vec::new();
for slot in &table.slots {
accessors.push(self.accessor(owner, visibility, slot)?);
}
let view_item = quote! {
#[doc = #doc]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(deprecated)]
#vis struct #view<'a> {
pub(crate) buf: &'a [u8],
pub(crate) table: usize,
}
#[allow(deprecated)]
impl<'a> #view<'a> {
#vis fn bytes(&self) -> &'a [u8] {
self.buf
}
#(#accessors)*
}
};
Ok(vec![
view_item,
self.table_encode_fn(owner, &ty, table)?,
self.table_verify_fn(owner, table)?,
self.table_decode_fn(owner, &ty, table)?,
])
}
fn accessor(
&self,
owner: &str,
visibility: i32,
slot: &Slot,
) -> Result<TokenStream, GenerateError> {
let vis = vis_tokens(visibility);
let name = ident(&snake_case(&slot.name));
let id = Literal::u16_suffixed(slot.id);
let width = Literal::usize_suffixed(slot.wire.inline_width());
let inner = self.view_expr(&slot.wire, "e! { __p })?;
let inner_ty = self.view_type(&slot.wire, &slot.field_type, &slot.hint, visibility);
let doc = format!(" Reads `{owner}`'s `{}` field in place.", name);
if slot.optional {
Ok(quote! {
#[doc = #doc]
#vis fn #name(&self) -> Option<#inner_ty> {
match ::ridl_rt::flatbuffers::field(self.buf, self.table, #id, #width) {
::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
::core::option::Option::Some(#inner)
}
_ => ::core::option::Option::None,
}
}
})
} else {
Ok(quote! {
#[doc = #doc]
#vis fn #name(&self) -> #inner_ty {
let __p = ::ridl_rt::flatbuffers::field(self.buf, self.table, #id, #width)
.unwrap_or(::core::option::Option::None)
.unwrap_or(0usize);
#inner
}
})
}
}
fn view_type(
&self,
wire: &Wire,
ty: &v2::FieldType,
hint: &str,
visibility: i32,
) -> TokenStream {
match wire {
Wire::Text(_) => quote! { &'a str },
Wire::Bytes(_) => quote! { &'a [u8] },
Wire::Table(name) | Wire::Union(name) => {
let view = view_path(name);
quote! { #view<'a> }
}
_ => {
let mut discard = Vec::new();
let bare = v2::FieldType {
optional: false,
kind: ty.kind.clone(),
};
field_type_tokens(&bare, hint, visibility, &mut discard)
}
}
}
fn view_expr(&self, wire: &Wire, at: &TokenStream) -> Result<TokenStream, GenerateError> {
Ok(match wire {
Wire::Text(_) => quote! {
::ridl_rt::flatbuffers::string(self.buf, #at).unwrap_or("")
},
Wire::Bytes(_) => quote! {
{
let __v = ::ridl_rt::flatbuffers::vector(self.buf, #at, 1usize)
.unwrap_or(::ridl_rt::flatbuffers::Vector { len: 0, first: 0 });
self.buf.get(__v.first..__v.first + __v.len).unwrap_or(&[])
}
},
Wire::Table(name) | Wire::Union(name) => {
let view = view_path(name);
quote! {
#view {
buf: self.buf,
table: ::ridl_rt::flatbuffers::follow(self.buf, #at).unwrap_or(0usize),
}
}
}
other => self.decode_expr(other, "e! { self.buf }, at)?,
})
}
fn table_encode_fn(
&self,
owner: &str,
ty: &Ident,
table: &Table,
) -> Result<TokenStream, GenerateError> {
let name = encode_ident(owner);
let body = self.table_encode_body(table, "e! { value })?;
let doc = format!(" Writes `{owner}` as a FlatBuffers table and returns its position.");
Ok(quote! {
#[doc = #doc]
#[allow(deprecated)]
pub(crate) fn #name(
value: &#ty,
builder: &mut ::ridl_rt::flatbuffers::Builder<'_>,
) -> ::core::result::Result<
::ridl_rt::flatbuffers::Pos,
::ridl_rt::payload::EncodeError,
> {
#body
}
})
}
fn table_encode_body(
&self,
table: &Table,
value: &TokenStream,
) -> Result<TokenStream, GenerateError> {
let size = Literal::usize_suffixed(table.size);
let align = Literal::usize_suffixed(table.align);
let slots = Literal::u16_suffixed(table.vtable_slots);
if table.slots.is_empty() {
return Ok(quote! { builder.push_table(#size, #align, #slots, &[]) });
}
let count = Literal::usize_suffixed(table.slots.len());
let mut writes: Vec<TokenStream> = Vec::new();
for slot in &table.slots {
let id = Literal::u16_suffixed(slot.id);
let offset = Literal::u16_suffixed(slot.offset);
if slot.optional {
let field = self.encode_field(&slot.wire, &Operand::borrowed(quote! { __v }))?;
let access = slot.operand(value).place;
writes.push(quote! {
if let ::core::option::Option::Some(__v) = &#access {
__fields[__n] = ::ridl_rt::flatbuffers::TableField {
slot: #id,
offset: #offset,
value: #field,
};
__n += 1;
}
});
} else {
let field = self.encode_field(&slot.wire, &slot.operand(value))?;
writes.push(quote! {
__fields[__n] = ::ridl_rt::flatbuffers::TableField {
slot: #id,
offset: #offset,
value: #field,
};
__n += 1;
});
}
}
Ok(quote! {
let mut __fields = [::ridl_rt::flatbuffers::TableField {
slot: 0u16,
offset: 4u16,
value: ::ridl_rt::flatbuffers::Field::Bool(false),
}; #count];
let mut __n = 0usize;
#(#writes)*
builder.push_table(#size, #align, #slots, &__fields[..__n])
})
}
fn encode_field(&self, wire: &Wire, expr: &Operand) -> Result<TokenStream, GenerateError> {
Ok(match wire {
Wire::Scalar(scalar) => {
let variant = format_ident!("{}", scalar.prim.field_variant());
let place = &expr.place;
let raw = scalar.raw(quote! { __s });
quote! {
{
let __s = #place;
::ridl_rt::flatbuffers::Field::#variant(#raw)
}
}
}
other => {
let pos = self.encode_pos(other, expr)?;
quote! { ::ridl_rt::flatbuffers::Field::Offset(#pos) }
}
})
}
fn encode_pos(&self, wire: &Wire, expr: &Operand) -> Result<TokenStream, GenerateError> {
let reference = &expr.reference;
Ok(match wire {
Wire::Scalar(_) => {
return Err(GenerateError {
message: "a FlatBuffers scalar is written inline, not out of line".to_string(),
});
}
Wire::Text(named) => {
let text = match named {
Some(_) => quote! { #reference.get() },
None => quote! { #reference.as_str() },
};
quote! { builder.push_string(#text)? }
}
Wire::Bytes(named) => {
let bytes = match named {
Some(_) => quote! { #reference.get() },
None => quote! { #reference.as_slice() },
};
quote! { builder.push_vector(#bytes, 1usize)? }
}
Wire::Table(name) | Wire::Union(name) => {
let call = encode_path(name);
quote! { #call(#reference, builder)? }
}
Wire::Vector { element, .. } => {
let stride = Literal::usize_suffixed(element.inline_width());
match element.as_ref() {
Wire::Scalar(scalar) => {
let raw = scalar.raw(quote! { __s });
quote! {
{
let __c = #reference;
let mut __bytes: Vec<u8> = Vec::with_capacity(__c.len() * #stride);
for __e in __c.iter() {
let __s = *__e;
let __r = #raw;
__bytes.extend_from_slice(&__r.to_le_bytes());
}
builder.push_vector(&__bytes, #stride)?
}
}
}
other => {
let pos = self.encode_pos(other, &Operand::borrowed(quote! { __e }))?;
quote! {
{
let __c = #reference;
let mut __offsets: Vec<::ridl_rt::flatbuffers::Pos> =
Vec::with_capacity(__c.len());
for __e in __c.iter() {
__offsets.push(#pos);
}
builder.push_offset_vector(&__offsets)?
}
}
}
}
}
Wire::Map { entry, .. } => {
let body = self.table_encode_body(entry, "e! { __e })?;
quote! {
{
let __c = #reference;
let mut __offsets: Vec<::ridl_rt::flatbuffers::Pos> =
Vec::with_capacity(__c.len());
for __e in __c.iter() {
__offsets.push({ #body }?);
}
builder.push_offset_vector(&__offsets)?
}
}
}
})
}
fn table_verify_fn(&self, owner: &str, table: &Table) -> Result<TokenStream, GenerateError> {
let name = verify_ident(owner);
let body = self.table_verify_body(owner, table)?;
let doc = format!(" Checks the FlatBuffers table at `table` against `{owner}`'s shape.");
Ok(quote! {
#[doc = #doc]
#[allow(deprecated)]
pub(crate) fn #name(
buf: &[u8],
table: usize,
) -> ::core::result::Result<(), ::ridl_rt::payload::VerifyError> {
#body
::core::result::Result::Ok(())
}
})
}
fn table_verify_body(&self, owner: &str, table: &Table) -> Result<TokenStream, GenerateError> {
let mut checks: Vec<TokenStream> = Vec::new();
for slot in &table.slots {
let id = Literal::u16_suffixed(slot.id);
let width = Literal::usize_suffixed(slot.wire.inline_width());
let present = self.verify_at(owner, &slot.wire, "e! { __p })?;
let absent = if slot.optional {
quote! { ::core::option::Option::None => {} }
} else {
quote! {
::core::option::Option::None => {
return ::core::result::Result::Err(
::ridl_rt::payload::VerifyError::Structure(
::ridl_rt::payload::Malformed::MissingRequired,
),
);
}
}
};
checks.push(quote! {
match ::ridl_rt::flatbuffers::field(buf, table, #id, #width)
.map_err(::ridl_rt::payload::VerifyError::Structure)?
{
::core::option::Option::Some(__p) => { #present }
#absent
}
});
}
Ok(quote! { #(#checks)* })
}
fn verify_at(
&self,
owner: &str,
wire: &Wire,
at: &TokenStream,
) -> Result<TokenStream, GenerateError> {
let buf = quote! { buf };
Ok(match wire {
Wire::Scalar(scalar) => {
let read = scalar.read(&buf, at);
match &scalar.repr {
Repr::Enum { name, .. } | Repr::EnumSet { name } => {
let ty = type_path(name);
let widened = scalar.widen(quote! { __raw });
quote! {
let __raw = #read
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
<#ty as ::core::convert::TryFrom<i64>>::try_from(#widened)
.map_err(::ridl_rt::payload::VerifyError::Contract)?;
}
}
Repr::Named(named) => {
let widened = scalar.widen(quote! { __raw });
match named_scalar_check(named, quote! { &(#widened) }) {
Some(check) => quote! {
let __raw = #read
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#check
},
None => quote! {
#read.map_err(::ridl_rt::payload::VerifyError::Structure)?;
},
}
}
_ => quote! {
#read.map_err(::ridl_rt::payload::VerifyError::Structure)?;
},
}
}
Wire::Text(named) => {
let check = named
.as_ref()
.and_then(|named| named_scalar_check(named, quote! { __s }));
match check {
Some(check) => quote! {
let __s = ::ridl_rt::flatbuffers::string(buf, #at)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#check
},
None => quote! {
::ridl_rt::flatbuffers::string(buf, #at)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
},
}
}
Wire::Bytes(named) => {
let check = named.as_ref().and_then(|named| {
named_scalar_check(named, quote! { &buf[__v.first..__v.first + __v.len] })
});
match check {
Some(check) => quote! {
let __v = ::ridl_rt::flatbuffers::vector(buf, #at, 1usize)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#check
},
None => quote! {
::ridl_rt::flatbuffers::vector(buf, #at, 1usize)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
},
}
}
Wire::Table(name) | Wire::Union(name) => {
let call = verify_path(name);
quote! {
let __t = ::ridl_rt::flatbuffers::follow(buf, #at)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#call(buf, __t)?;
}
}
Wire::Vector { element, min, max } => {
let stride = Literal::usize_suffixed(element.inline_width());
let inner = self.verify_at(owner, element, "e! { __at })?;
let count = count_check(owner, *min, *max);
quote! {
let __v = ::ridl_rt::flatbuffers::vector(buf, #at, #stride)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#count
for __i in 0..__v.len {
let __at = __v.element(__i, #stride);
#inner
}
}
}
Wire::Map { entry, min, max } => {
let inner = self.table_verify_body(owner, entry)?;
let count = count_check(owner, *min, *max);
quote! {
let __v = ::ridl_rt::flatbuffers::vector(buf, #at, 4usize)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#count
for __i in 0..__v.len {
let table = ::ridl_rt::flatbuffers::follow(buf, __v.element(__i, 4usize))
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#inner
}
}
}
})
}
fn table_decode_fn(
&self,
owner: &str,
ty: &Ident,
table: &Table,
) -> Result<TokenStream, GenerateError> {
let name = decode_ident(owner);
let mut fields: Vec<TokenStream> = Vec::new();
for slot in &table.slots {
let field = ident(&snake_case(&slot.name));
let value = self.slot_decode_expr(slot, "e! { buf }, "e! { table })?;
fields.push(quote! { #field: #value });
}
let doc = format!(" Builds `{owner}` from the FlatBuffers table at `table`.");
Ok(quote! {
#[doc = #doc]
#[allow(deprecated)]
pub(crate) fn #name(buf: &[u8], table: usize) -> #ty {
#ty { #(#fields),* }
}
})
}
fn slot_decode_expr(
&self,
slot: &Slot,
buf: &TokenStream,
table: &TokenStream,
) -> Result<TokenStream, GenerateError> {
let id = Literal::u16_suffixed(slot.id);
let width = Literal::usize_suffixed(slot.wire.inline_width());
let inner = self.decode_expr(&slot.wire, buf, "e! { __p })?;
Ok(if slot.optional {
quote! {
match ::ridl_rt::flatbuffers::field(#buf, #table, #id, #width) {
::core::result::Result::Ok(::core::option::Option::Some(__p)) => {
::core::option::Option::Some(#inner)
}
_ => ::core::option::Option::None,
}
}
} else {
quote! {
{
let __p = ::ridl_rt::flatbuffers::field(#buf, #table, #id, #width)
.unwrap_or(::core::option::Option::None)
.unwrap_or(0usize);
#inner
}
}
})
}
fn decode_expr(
&self,
wire: &Wire,
buf: &TokenStream,
at: &TokenStream,
) -> Result<TokenStream, GenerateError> {
Ok(match wire {
Wire::Scalar(scalar) => scalar.decode(buf, at),
Wire::Text(named) => {
let text = quote! {
String::from(::ridl_rt::flatbuffers::string(#buf, #at).unwrap_or(""))
};
match named {
Some(named) => {
let ty = type_path(&named.name);
let ctor = format_ident!("{}", named.ctor);
quote! { #ty::#ctor(#text) }
}
None => text,
}
}
Wire::Bytes(named) => {
let bytes = quote! {
{
let __v = ::ridl_rt::flatbuffers::vector(#buf, #at, 1usize)
.unwrap_or(::ridl_rt::flatbuffers::Vector { len: 0, first: 0 });
#buf.get(__v.first..__v.first + __v.len).unwrap_or(&[]).to_vec()
}
};
match named {
Some(named) => {
let ty = type_path(&named.name);
let ctor = format_ident!("{}", named.ctor);
quote! { #ty::#ctor(#bytes) }
}
None => bytes,
}
}
Wire::Table(name) | Wire::Union(name) => {
let call = decode_path(name);
quote! {
#call(#buf, ::ridl_rt::flatbuffers::follow(#buf, #at).unwrap_or(0usize))
}
}
Wire::Vector { element, min, max } => {
let stride = Literal::usize_suffixed(element.inline_width());
let inner = self.decode_expr(element, buf, "e! { __at })?;
if min == max {
quote! {
{
let __v = ::ridl_rt::flatbuffers::vector(#buf, #at, #stride)
.unwrap_or(::ridl_rt::flatbuffers::Vector { len: 0, first: 0 });
::core::array::from_fn(|__i| {
let __at = __v.element(__i, #stride);
#inner
})
}
}
} else {
quote! {
{
let __v = ::ridl_rt::flatbuffers::vector(#buf, #at, #stride)
.unwrap_or(::ridl_rt::flatbuffers::Vector { len: 0, first: 0 });
let mut __out = Vec::with_capacity(__v.len);
for __i in 0..__v.len {
let __at = __v.element(__i, #stride);
__out.push(#inner);
}
__out
}
}
}
}
Wire::Map { entry, .. } => {
let key = self.slot_decode_expr(&entry.slots[0], buf, "e! { __t })?;
let value = self.slot_decode_expr(&entry.slots[1], buf, "e! { __t })?;
quote! {
{
let __v = ::ridl_rt::flatbuffers::vector(#buf, #at, 4usize)
.unwrap_or(::ridl_rt::flatbuffers::Vector { len: 0, first: 0 });
let mut __out = Vec::with_capacity(__v.len);
for __i in 0..__v.len {
let __t = ::ridl_rt::flatbuffers::follow(
#buf,
__v.element(__i, 4usize),
)
.unwrap_or(0usize);
__out.push((#key, #value));
}
__out
}
}
}
})
}
fn union_layout(&self) -> (u16, u16, usize, usize) {
let (offsets, size, align) = place(&[1, 4]);
(offsets[0], offsets[1], size, align)
}
fn union_items(
&self,
decl: &v2::Decl,
def: &v2::UnionDef,
) -> Result<Vec<TokenStream>, GenerateError> {
let owner = decl.name.as_str();
if def.arms.is_empty() {
return Err(GenerateError {
message: format!("`{owner}` declares no arm, so it encodes no value"),
});
}
let vis = vis_tokens(decl.visibility);
let ty = ident(owner);
let view = view_ident(owner);
let (disc_offset, value_offset, size, align) = self.union_layout();
let disc_offset = Literal::u16_suffixed(disc_offset);
let value_offset = Literal::u16_suffixed(value_offset);
let size = Literal::usize_suffixed(size);
let align = Literal::usize_suffixed(align);
let wrapper_slots = Literal::u16_suffixed(
u16::try_from(fb_projection::UNION_WRAPPER_VALUE_ID + 1).unwrap(),
);
let mut arms: Vec<UnionArm> = Vec::new();
for arm in &def.arms {
arms.push(self.union_arm(owner, arm)?);
}
let encode_name = encode_ident(owner);
let verify_name = verify_ident(owner);
let decode_name = decode_ident(owner);
let mut encode_arms: Vec<TokenStream> = Vec::new();
let mut verify_arms: Vec<TokenStream> = Vec::new();
let mut decode_arms: Vec<TokenStream> = Vec::new();
for arm in &arms {
let variant = ident(&camel_case(&arm.name));
let tag = Literal::u8_suffixed(arm.tag);
let write = &arm.encode;
encode_arms.push(quote! { #ty::#variant(__a) => (#tag, #write) });
let check = &arm.verify;
verify_arms.push(quote! { #tag => { #check } });
let build = &arm.decode;
decode_arms.push(quote! { #tag => #ty::#variant(#build) });
}
let fallback = {
let first = &arms[0];
let variant = ident(&camel_case(&first.name));
let build = &first.decode;
quote! { _ => #ty::#variant(#build) }
};
let union_malformed = quote! {
::core::result::Result::Err(::ridl_rt::payload::VerifyError::Structure(
::ridl_rt::payload::Malformed::Union,
))
};
let doc =
format!(" A zero-copy accessor over FlatBuffers bytes `{owner}`'s `verify` accepted.");
let value_doc = format!(
" The value the buffer carries. A union's arms have no one view type, so this \
decodes `{owner}` rather than borrowing it."
);
Ok(vec![
quote! {
#[doc = #doc]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(deprecated)]
#vis struct #view<'a> {
pub(crate) buf: &'a [u8],
pub(crate) table: usize,
}
#[allow(deprecated)]
impl<'a> #view<'a> {
#vis fn bytes(&self) -> &'a [u8] {
self.buf
}
#[doc = #value_doc]
#vis fn value(&self) -> #ty {
#decode_name(self.buf, self.table)
}
}
},
quote! {
#[allow(deprecated)]
pub(crate) fn #encode_name(
value: &#ty,
builder: &mut ::ridl_rt::flatbuffers::Builder<'_>,
) -> ::core::result::Result<
::ridl_rt::flatbuffers::Pos,
::ridl_rt::payload::EncodeError,
> {
let (__d, __v) = match value {
#(#encode_arms),*
};
let __fields = [
::ridl_rt::flatbuffers::TableField {
slot: 0u16,
offset: #disc_offset,
value: ::ridl_rt::flatbuffers::Field::U8(__d),
},
::ridl_rt::flatbuffers::TableField {
slot: 1u16,
offset: #value_offset,
value: ::ridl_rt::flatbuffers::Field::Offset(__v),
},
];
builder.push_table(#size, #align, #wrapper_slots, &__fields)
}
},
quote! {
#[allow(deprecated)]
pub(crate) fn #verify_name(
buf: &[u8],
table: usize,
) -> ::core::result::Result<(), ::ridl_rt::payload::VerifyError> {
let __dp = ::ridl_rt::flatbuffers::field(buf, table, 0u16, 1usize)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
let __vp = ::ridl_rt::flatbuffers::field(buf, table, 1u16, 4usize)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
let (__d, __value) = match (__dp, __vp) {
(
::core::option::Option::Some(__d),
::core::option::Option::Some(__value),
) => (
::ridl_rt::flatbuffers::read_u8(buf, __d)
.map_err(::ridl_rt::payload::VerifyError::Structure)?,
__value,
),
_ => return #union_malformed,
};
match __d {
#(#verify_arms)*
_ => return #union_malformed,
}
::core::result::Result::Ok(())
}
},
quote! {
#[allow(deprecated)]
pub(crate) fn #decode_name(buf: &[u8], table: usize) -> #ty {
let __d = ::ridl_rt::flatbuffers::field(buf, table, 0u16, 1usize)
.unwrap_or(::core::option::Option::None)
.map(|__p| ::ridl_rt::flatbuffers::read_u8(buf, __p).unwrap_or(0u8))
.unwrap_or(0u8);
let __value = ::ridl_rt::flatbuffers::field(buf, table, 1u16, 4usize)
.unwrap_or(::core::option::Option::None)
.unwrap_or(0usize);
match __d {
#(#decode_arms,)*
#fallback
}
}
},
])
}
fn box_bodies(
&self,
owner: &str,
wire: &Wire,
table: &fb_projection::TableLayout,
value: &Operand,
at: &TokenStream,
) -> Result<BoxBodies, GenerateError> {
let [slot] = table.slots.as_slice() else {
return Err(GenerateError {
message: format!(
"the FlatBuffers projection describes a box table with {} slots, and a box \
holds exactly one value (ADR-0019 decisions 2 and 8)",
table.slots.len()
),
});
};
let id = u16::try_from(slot.id).map_err(|_| GenerateError {
message: format!(
"the FlatBuffers projection puts a box table's value at id {}, which a vtable \
cannot carry",
slot.id
),
})?;
let id_lit = Literal::u16_suffixed(id);
let slots = Literal::u16_suffixed(u16::try_from(table.vtable_slots()).map_err(|_| {
GenerateError {
message: "a box table's vtable does not fit a u16".to_string(),
}
})?);
let width = wire.inline_width();
let (offsets, size, align) = place(&[width]);
let offset = Literal::u16_suffixed(offsets[0]);
let size = Literal::usize_suffixed(size);
let align = Literal::usize_suffixed(align);
let width_lit = Literal::usize_suffixed(width);
let field = self.encode_field(wire, value)?;
let inner_verify = self.verify_at(owner, wire, "e! { __p })?;
let inner_decode = self.decode_expr(wire, "e! { buf }, "e! { __p })?;
Ok(BoxBodies {
encode: quote! {
{
let __box = [::ridl_rt::flatbuffers::TableField {
slot: #id_lit,
offset: #offset,
value: #field,
}];
builder.push_table(#size, #align, #slots, &__box)?
}
},
verify: quote! {
match ::ridl_rt::flatbuffers::field(buf, #at, #id_lit, #width_lit)
.map_err(::ridl_rt::payload::VerifyError::Structure)?
{
::core::option::Option::Some(__p) => { #inner_verify }
::core::option::Option::None => {
return ::core::result::Result::Err(
::ridl_rt::payload::VerifyError::Structure(
::ridl_rt::payload::Malformed::MissingRequired,
),
);
}
}
},
decode: quote! {
{
let __p = ::ridl_rt::flatbuffers::field(buf, #at, #id_lit, #width_lit)
.unwrap_or(::core::option::Option::None)
.unwrap_or(0usize);
#inner_decode
}
},
})
}
fn union_arm(&self, owner: &str, arm: &v2::UnionArm) -> Result<UnionArm, GenerateError> {
let tag = u8::try_from(arm.ordinal).map_err(|_| GenerateError {
message: format!(
"`{owner}.{}` has ordinal {}, and a FlatBuffers union discriminant is a ubyte",
arm.name, arm.ordinal
),
})?;
let wire = self.wire(
&v2::FieldType {
optional: false,
kind: Some(v2::field_type::Kind::Named(arm.type_ref.clone())),
},
"",
)?;
match &wire {
Wire::Table(_) | Wire::Union(_) => Ok(UnionArm {
name: arm.name.clone(),
tag,
encode: self.encode_pos(&wire, &Operand::borrowed(quote! { __a }))?,
verify: self.verify_at(owner, &wire, "e! { __value })?,
decode: self.decode_expr(&wire, "e! { buf }, "e! { __value })?,
}),
_ => {
let bodies = self.box_bodies(
owner,
&wire,
&fb_projection::union_arm_box_table(),
&Operand::borrowed(quote! { __a }),
"e! { __t },
)?;
let verify = &bodies.verify;
let decode = &bodies.decode;
Ok(UnionArm {
name: arm.name.clone(),
tag,
encode: bodies.encode.clone(),
verify: quote! {
let __t = ::ridl_rt::flatbuffers::follow(buf, __value)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#verify
},
decode: quote! {
{
let __t = ::ridl_rt::flatbuffers::follow(buf, __value)
.unwrap_or(0usize);
#decode
}
},
})
}
}
}
fn root_box_items(&self, decl: &v2::Decl) -> Result<Vec<TokenStream>, GenerateError> {
let owner = decl.name.as_str();
let wire = self.wire(
&v2::FieldType {
optional: false,
kind: Some(v2::field_type::Kind::Named(owner.to_string())),
},
"",
)?;
let vis = vis_tokens(decl.visibility);
let ty = ident(owner);
let view = view_ident(owner);
let encode_name = encode_ident(owner);
let verify_name = verify_ident(owner);
let decode_name = decode_ident(owner);
let bodies = self.box_bodies(
owner,
&wire,
&fb_projection::root_box_table(),
&Operand::borrowed(quote! { value }),
"e! { table },
)?;
let encode_body = &bodies.encode;
let verify_body = &bodies.verify;
let decode_body = &bodies.decode;
let doc = format!(" An accessor over FlatBuffers bytes `{owner}`'s `verify` accepted.");
let value_doc = if matches!(wire, Wire::Text(_) | Wire::Bytes(_)) {
format!(
" The value the box carries. `{owner}` owns its bytes, so this allocates — \
unlike a struct field of the same type, which a view borrows in place."
)
} else {
format!(
" The value the box carries. `{owner}` is one value, so this decodes it rather \
than borrowing it, which costs one read."
)
};
let encode_doc = format!(
" Writes `{owner}` as its box table and returns its position (ADR-0019 decision 8)."
);
Ok(vec![
quote! {
#[doc = #doc]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[allow(deprecated)]
#vis struct #view<'a> {
pub(crate) buf: &'a [u8],
pub(crate) table: usize,
}
#[allow(deprecated)]
impl<'a> #view<'a> {
#vis fn bytes(&self) -> &'a [u8] {
self.buf
}
#[doc = #value_doc]
#vis fn value(&self) -> #ty {
#decode_name(self.buf, self.table)
}
}
},
quote! {
#[doc = #encode_doc]
#[allow(deprecated)]
pub(crate) fn #encode_name(
value: &#ty,
builder: &mut ::ridl_rt::flatbuffers::Builder<'_>,
) -> ::core::result::Result<
::ridl_rt::flatbuffers::Pos,
::ridl_rt::payload::EncodeError,
> {
::core::result::Result::Ok(#encode_body)
}
},
quote! {
#[allow(deprecated)]
pub(crate) fn #verify_name(
buf: &[u8],
table: usize,
) -> ::core::result::Result<(), ::ridl_rt::payload::VerifyError> {
#verify_body
::core::result::Result::Ok(())
}
},
quote! {
#[allow(deprecated)]
pub(crate) fn #decode_name(buf: &[u8], table: usize) -> #ty {
#decode_body
}
},
])
}
fn payload_impl(&self, decl: &v2::Decl) -> Result<TokenStream, GenerateError> {
let owner = decl.name.as_str();
let bound =
fb_projection::max_size(self.packages(), decl).ok_or_else(|| GenerateError {
message: format!("`{owner}` has no finite FlatBuffers bound"),
})?;
let ty = ident(owner);
let view = view_ident(owner);
let encode = encode_ident(owner);
let verify = verify_ident(owner);
let decode = decode_ident(owner);
let max = Literal::usize_suffixed(bound as usize);
let align = Literal::usize_suffixed(BUFFER_ALIGN);
let max_doc = format!(
" The largest FlatBuffers buffer any legal `{owner}` encodes to: {bound} bytes."
);
Ok(quote! {
#[allow(deprecated)]
impl ::ridl_rt::payload::Payload<::ridl_rt::encoding::FlatBuffers> for #ty {
#[doc = #max_doc]
const MAX_SIZE: usize = #max;
type View<'a> = #view<'a>;
fn encode<'o>(
&self,
out: &'o mut [u8],
) -> ::core::result::Result<
::ridl_rt::payload::Encoded<'o, Self::View<'o>>,
::ridl_rt::payload::EncodeError,
> {
let mut builder = ::ridl_rt::flatbuffers::Builder::new(out);
let __root = #encode(self, &mut builder)?;
let bytes = builder.finish(__root, #align)?;
let table = ::ridl_rt::flatbuffers::root(bytes).unwrap_or(0usize);
::core::result::Result::Ok(::ridl_rt::payload::Encoded {
bytes,
view: #view { buf: bytes, table },
})
}
fn verify(
buf: &[u8],
) -> ::core::result::Result<Self::View<'_>, ::ridl_rt::payload::VerifyError> {
if buf.len()
> <Self as ::ridl_rt::payload::Payload<
::ridl_rt::encoding::FlatBuffers,
>>::MAX_SIZE
{
return ::core::result::Result::Err(
::ridl_rt::payload::VerifyError::Structure(
::ridl_rt::payload::Malformed::TooLarge,
),
);
}
let table = ::ridl_rt::flatbuffers::root(buf)
.map_err(::ridl_rt::payload::VerifyError::Structure)?;
#verify(buf, table)?;
::core::result::Result::Ok(#view { buf, table })
}
fn decode(
r: ::ridl_rt::payload::Ref<'_, Self, ::ridl_rt::encoding::FlatBuffers>,
) -> Self {
let __view = r.view();
#decode(__view.buf, __view.table)
}
}
})
}
}
struct BoxBodies {
encode: TokenStream,
verify: TokenStream,
decode: TokenStream,
}
struct UnionArm {
name: String,
tag: u8,
encode: TokenStream,
verify: TokenStream,
decode: TokenStream,
}
fn count_check(owner: &str, min: u64, max: u64) -> TokenStream {
let violation = quote! {
return ::core::result::Result::Err(::ridl_rt::payload::VerifyError::Contract(
::ridl_rt::payload::Violation {
type_name: #owner,
rule: ::ridl_rt::payload::Rule::Length,
},
));
};
let high = Literal::usize_suffixed(max as usize);
if min == max {
return quote! {
if __v.len != #high {
#violation
}
};
}
if min == 0 {
return quote! {
if __v.len > #high {
#violation
}
};
}
let low = Literal::usize_suffixed(min as usize);
quote! {
if __v.len < #low || __v.len > #high {
#violation
}
}
}
fn scalar_repr(named: Option<NamedScalar>, backing: ScalarBacking) -> Repr {
match named {
Some(named) => Repr::Named(named),
None => match backing {
ScalarBacking::Boolean => Repr::Bool,
ScalarBacking::Integer => Repr::Int,
_ => Repr::Float,
},
}
}
fn int_prim(width: i32) -> Option<Prim> {
match v2::IntWidth::try_from(width).ok()? {
v2::IntWidth::U8 => Some(Prim::U8),
v2::IntWidth::I8 => Some(Prim::I8),
v2::IntWidth::U16 => Some(Prim::U16),
v2::IntWidth::I16 => Some(Prim::I16),
v2::IntWidth::U32 => Some(Prim::U32),
v2::IntWidth::I32 => Some(Prim::I32),
v2::IntWidth::U64 => Some(Prim::U64),
v2::IntWidth::I64 => Some(Prim::I64),
v2::IntWidth::Unspecified => None,
}
}