use super::{AstPath, Box, Expr, ParameterList, StateMutability, Visibility};
use solar_interface::{Ident, Span, Spanned, Symbol, kw};
use std::{borrow::Cow, fmt};
#[derive(Debug)]
pub struct Type<'ast> {
pub span: Span,
pub kind: TypeKind<'ast>,
}
impl Type<'_> {
#[inline]
pub fn is_elementary(&self) -> bool {
self.kind.is_elementary()
}
#[inline]
pub fn is_custom(&self) -> bool {
self.kind.is_custom()
}
#[inline]
pub fn is_function(&self) -> bool {
matches!(self.kind, TypeKind::Function(_))
}
}
pub enum TypeKind<'ast> {
Elementary(ElementaryType),
Array(Box<'ast, TypeArray<'ast>>),
Function(Box<'ast, TypeFunction<'ast>>),
Mapping(Box<'ast, TypeMapping<'ast>>),
Custom(AstPath<'ast>),
}
impl fmt::Debug for TypeKind<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Elementary(ty) => ty.fmt(f),
Self::Array(ty) => ty.fmt(f),
Self::Function(ty) => ty.fmt(f),
Self::Mapping(ty) => ty.fmt(f),
Self::Custom(path) => write!(f, "Custom({path:?})"),
}
}
}
impl TypeKind<'_> {
pub fn is_elementary(&self) -> bool {
matches!(self, Self::Elementary(_))
}
pub fn is_custom(&self) -> bool {
matches!(self, Self::Custom(_))
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElementaryType {
Address( bool),
Bool,
String,
Bytes,
Fixed(TypeSize, TypeFixedSize),
UFixed(TypeSize, TypeFixedSize),
Int(TypeSize),
UInt(TypeSize),
FixedBytes(TypeSize),
}
impl fmt::Debug for ElementaryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Address(false) => f.write_str("Address"),
Self::Address(true) => f.write_str("AddressPayable"),
Self::Bool => f.write_str("Bool"),
Self::String => f.write_str("String"),
Self::Bytes => f.write_str("Bytes"),
Self::Fixed(size, fixed) => write!(f, "Fixed({}, {})", size.bytes_raw(), fixed.get()),
Self::UFixed(size, fixed) => write!(f, "UFixed({}, {})", size.bytes_raw(), fixed.get()),
Self::Int(size) => write!(f, "Int({})", size.bits_raw()),
Self::UInt(size) => write!(f, "UInt({})", size.bits_raw()),
Self::FixedBytes(size) => write!(f, "FixedBytes({})", size.bytes_raw()),
}
}
}
impl fmt::Display for ElementaryType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.write_abi_str(f)?;
if let Self::Address(true) = self {
f.write_str(" payable")?;
}
Ok(())
}
}
impl ElementaryType {
pub fn to_abi_str(self) -> Cow<'static, str> {
match self {
Self::Address(_) => "address".into(),
Self::Bool => "bool".into(),
Self::String => "string".into(),
Self::Bytes => "bytes".into(),
Self::Fixed(_size, _fixed) => "fixed".into(),
Self::UFixed(_size, _fixed) => "ufixed".into(),
Self::Int(size) => format!("int{}", size.bits()).into(),
Self::UInt(size) => format!("uint{}", size.bits()).into(),
Self::FixedBytes(size) => format!("bytes{}", size.bytes()).into(),
}
}
pub fn write_abi_str<W: fmt::Write + ?Sized>(self, f: &mut W) -> fmt::Result {
f.write_str(match self {
Self::Address(_) => "address",
Self::Bool => "bool",
Self::String => "string",
Self::Bytes => "bytes",
Self::Fixed(m, n) => return write!(f, "fixed{}x{}", m.bits(), n.get()),
Self::UFixed(m, n) => return write!(f, "ufixed{}x{}", m.bits(), n.get()),
Self::Int(size) => return write!(f, "int{}", size.bits()),
Self::UInt(size) => return write!(f, "uint{}", size.bits()),
Self::FixedBytes(size) => return write!(f, "bytes{}", size.bytes()),
})
}
#[inline]
pub const fn is_value_type(self) -> bool {
matches!(
self,
Self::Address(_)
| Self::Bool
| Self::Fixed(..)
| Self::UFixed(..)
| Self::Int(..)
| Self::UInt(..)
| Self::FixedBytes(..)
)
}
#[inline]
pub const fn is_reference_type(self) -> bool {
matches!(self, Self::String | Self::Bytes)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypeSize(u16);
impl Default for TypeSize {
#[inline]
fn default() -> Self {
Self::ZERO
}
}
impl fmt::Debug for TypeSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TypeSize({})", self.0)
}
}
impl TypeSize {
pub const ZERO: Self = Self(0);
pub const MAX: u16 = 256;
#[inline]
const fn new(bits: u16) -> Option<Self> {
if bits > Self::MAX { None } else { Some(Self(bits)) }
}
#[inline]
#[track_caller]
pub fn new_int_bits(bits: u16) -> Self {
Self::try_new_int_bits(bits).unwrap_or_else(|| panic!("invalid integer size: {bits}"))
}
#[inline]
pub fn try_new_int_bits(bits: u16) -> Option<Self> {
if bits.is_multiple_of(8) { Self::new(bits) } else { None }
}
#[inline]
#[track_caller]
pub fn new_literal_bits(bits: u16) -> Self {
Self::try_new_literal_bits(bits).unwrap_or_else(|| panic!("invalid literal size: {bits}"))
}
#[inline]
pub fn try_new_literal_bits(bits: u16) -> Option<Self> {
if bits == 0 { None } else { Self::new(bits) }
}
#[inline]
#[track_caller]
pub fn new_fb_bytes(bytes: u8) -> Self {
Self::try_new_fb_bytes(bytes).unwrap_or_else(|| panic!("invalid fixed-bytes size: {bytes}"))
}
#[inline]
pub fn try_new_fb_bytes(bytes: u8) -> Option<Self> {
if bytes == 0 {
return None;
}
Self::new(bytes as u16 * 8)
}
#[inline]
pub const fn bytes(self) -> u8 {
if self.0 == 0 { (Self::MAX / 8) as u8 } else { self.0.div_ceil(8) as u8 }
}
#[inline]
pub const fn bytes_raw(self) -> u8 {
self.0.div_ceil(8) as u8
}
#[inline]
pub const fn bits(self) -> u16 {
if self.0 == 0 { Self::MAX } else { self.0 }
}
#[inline]
pub const fn bits_raw(self) -> u16 {
self.0
}
#[inline]
pub const fn int_keyword(self) -> Symbol {
kw::int(self.bytes_raw())
}
#[inline]
pub const fn uint_keyword(self) -> Symbol {
kw::uint(self.bytes_raw())
}
#[inline]
#[track_caller]
pub const fn bytes_keyword(self) -> Symbol {
kw::fixed_bytes(self.bytes_raw())
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TypeFixedSize(u8);
impl fmt::Debug for TypeFixedSize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "TypeFixedSize({})", self.0)
}
}
impl TypeFixedSize {
pub const ZERO: Self = Self(0);
pub const MAX: u8 = 80;
#[inline]
pub const fn new(value: u8) -> Option<Self> {
if value > Self::MAX { None } else { Some(Self(value)) }
}
#[inline]
pub const fn get(self) -> u8 {
self.0
}
}
#[derive(Debug)]
pub struct TypeArray<'ast> {
pub element: Type<'ast>,
pub size: Option<Box<'ast, Expr<'ast>>>,
}
#[derive(Debug)]
pub struct TypeFunction<'ast> {
pub parameters: ParameterList<'ast>,
pub visibility: Option<Spanned<Visibility>>,
pub state_mutability: Option<Spanned<StateMutability>>,
pub returns: Option<ParameterList<'ast>>,
}
impl<'ast> TypeFunction<'ast> {
pub fn visibility(&self) -> Option<Visibility> {
self.visibility.map(Spanned::into_inner)
}
pub fn state_mutability(&self) -> StateMutability {
self.state_mutability.map(Spanned::into_inner).unwrap_or(StateMutability::NonPayable)
}
pub fn returns(&self) -> &[crate::VariableDefinition<'ast>] {
self.returns.as_ref().map(|pl| &pl.vars[..]).unwrap_or(&[])
}
}
#[derive(Debug)]
pub struct TypeMapping<'ast> {
pub key: Type<'ast>,
pub key_name: Option<Ident>,
pub value: Type<'ast>,
pub value_name: Option<Ident>,
}