use alloc::{string::ToString, vec::Vec};
use proc_macro2::{Span, TokenStream};
use quote::ToTokens;
use syn::{
FieldsNamed, FieldsUnnamed, Ident, LitInt, Type,
parse::{Parse, ParseStream},
};
use crate::resolve::Resolve;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FieldId(usize);
impl FieldId {
#[inline]
#[must_use]
pub const fn index(self) -> usize {
let Self(index) = self;
index
}
#[inline]
#[must_use]
pub const fn from_index(index: usize) -> Self {
Self(index)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum FieldRef {
Named(Ident),
Indexed(usize),
}
impl Parse for FieldRef {
#[inline]
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Ident) {
return input.parse().map(Self::Named);
}
if lookahead.peek(LitInt) {
let literal: LitInt = input.parse()?;
let index = literal
.base10_parse::<usize>()
.map_err(|_| syn::Error::new_spanned(&literal, "expected valid field index"))?;
return Ok(Self::Indexed(index));
}
Err(lookahead.error())
}
}
impl ToTokens for FieldRef {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Named(name) => name.to_tokens(tokens),
Self::Indexed(index) => {
LitInt::new(index.to_string().as_str(), Span::call_site()).to_tokens(tokens);
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Shape {
Named,
Unnamed,
Unit,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Field {
name: Option<Ident>,
ty: Type,
}
impl Field {
#[inline]
#[must_use]
pub const fn name(&self) -> Option<&Ident> {
let Self { name, .. } = self;
name.as_ref()
}
#[inline]
#[must_use]
pub const fn ty(&self) -> &Type {
let Self { ty, .. } = self;
ty
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Fields {
shape: Shape,
fields: Vec<Field>,
}
impl Fields {
#[inline]
pub fn from_syn(fields: &syn::Fields) -> syn::Result<Self> {
match fields {
syn::Fields::Named(FieldsNamed { named, .. }) => {
let fields = named
.iter()
.map(|syn::Field { ident, ty, .. }| {
let name = ident
.clone()
.ok_or_else(|| syn::Error::new_spanned(ident, "expected a named field"))?;
let ty = ty.clone();
Ok(Field { name: Some(name), ty })
})
.collect::<syn::Result<Vec<_>>>()?;
let shape = Shape::Named;
Ok(Self { shape, fields })
}
syn::Fields::Unnamed(FieldsUnnamed { unnamed, .. }) => {
let fields = unnamed
.iter()
.map(|syn::Field { ty, .. }| Field {
name: None,
ty: ty.clone(),
})
.collect();
let shape = Shape::Unnamed;
Ok(Self { shape, fields })
}
syn::Fields::Unit => {
let shape = Shape::Unit;
let fields = Vec::new();
Ok(Self { shape, fields })
}
}
}
#[inline]
#[must_use]
pub const fn shape(&self) -> Shape {
let Self { shape, .. } = self;
*shape
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
let Self { fields, .. } = self;
fields.len()
}
#[inline]
#[must_use]
pub fn get(&self, field: FieldId) -> &Field {
let Self { fields, .. } = self;
fields
.get(field.index())
.expect("validated field identity must remain within its originating field collection")
}
#[inline]
#[must_use]
pub fn ty(&self, field: FieldId) -> &Type {
self.get(field).ty()
}
#[inline]
#[must_use]
pub fn name(&self, field: FieldId) -> Option<&Ident> {
self.get(field).name()
}
#[inline]
pub fn sole(&self) -> syn::Result<FieldId> {
let Self { fields, .. } = self;
if fields.len() == 1 {
Ok(FieldId::from_index(0))
} else {
Err(syn::Error::new(Span::call_site(), "expected exactly one field"))
}
}
#[inline]
pub fn named(&self, name: &Ident) -> syn::Result<FieldId> {
let Self { shape, fields } = self;
match shape {
Shape::Named => fields
.iter()
.position(|field| field.name() == Some(name))
.map(FieldId::from_index)
.ok_or_else(|| syn::Error::new_spanned(name, "unknown named field")),
Shape::Unnamed => Err(syn::Error::new_spanned(name, "named field reference used with tuple fields")),
Shape::Unit => Err(syn::Error::new_spanned(name, "unit fields cannot contain a field reference")),
}
}
#[inline]
pub fn indexed(&self, index: usize) -> syn::Result<FieldId> {
let Self { shape, fields } = self;
match shape {
Shape::Named => Err(syn::Error::new(Span::call_site(), "indexed field reference used with named fields")),
Shape::Unnamed if index < fields.len() => Ok(FieldId::from_index(index)),
Shape::Unnamed => Err(syn::Error::new(Span::call_site(), "tuple field index is out of bounds")),
Shape::Unit => Err(syn::Error::new(Span::call_site(), "unit fields cannot contain a field reference")),
}
}
}
impl Resolve for FieldRef {
type Context = Fields;
type Output = FieldId;
#[inline]
fn resolve(self, fields: &Self::Context) -> syn::Result<Self::Output> {
match self {
Self::Named(name) => fields.named(&name),
Self::Indexed(index) => fields.indexed(index),
}
}
}