use super::{
AstPath, BinOpKind, Block, Box, CallArgs, DocComments, Expr, SemverReq, StrLit, Type, UnOpKind,
};
use crate::{BoxSlice, token::Token};
use either::Either;
use solar_interface::{Ident, Span, Spanned, Symbol};
use std::{
fmt,
ops::{Deref, DerefMut},
};
use strum::EnumIs;
#[derive(Debug, Default)]
pub struct ParameterList<'ast> {
pub span: Span,
pub vars: BoxSlice<'ast, VariableDefinition<'ast>>,
}
impl<'ast> Deref for ParameterList<'ast> {
type Target = BoxSlice<'ast, VariableDefinition<'ast>>;
fn deref(&self) -> &Self::Target {
&self.vars
}
}
impl<'ast> DerefMut for ParameterList<'ast> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.vars
}
}
#[derive(Debug)]
pub struct Item<'ast> {
pub docs: DocComments<'ast>,
pub span: Span,
pub kind: ItemKind<'ast>,
}
impl Item<'_> {
pub fn name(&self) -> Option<Ident> {
self.kind.name()
}
pub fn description(&self) -> &'static str {
self.kind.description()
}
pub fn is_allowed_in_contract(&self) -> bool {
self.kind.is_allowed_in_contract()
}
}
pub enum ItemKind<'ast> {
Pragma(PragmaDirective<'ast>),
Import(ImportDirective<'ast>),
Using(UsingDirective<'ast>),
Contract(ItemContract<'ast>),
Function(ItemFunction<'ast>),
Variable(VariableDefinition<'ast>),
Struct(ItemStruct<'ast>),
Enum(ItemEnum<'ast>),
Udvt(ItemUdvt<'ast>),
Error(ItemError<'ast>),
Event(ItemEvent<'ast>),
}
impl fmt::Debug for ItemKind<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ItemKind::")?;
match self {
ItemKind::Pragma(item) => item.fmt(f),
ItemKind::Import(item) => item.fmt(f),
ItemKind::Using(item) => item.fmt(f),
ItemKind::Contract(item) => item.fmt(f),
ItemKind::Function(item) => item.fmt(f),
ItemKind::Variable(item) => item.fmt(f),
ItemKind::Struct(item) => item.fmt(f),
ItemKind::Enum(item) => item.fmt(f),
ItemKind::Udvt(item) => item.fmt(f),
ItemKind::Error(item) => item.fmt(f),
ItemKind::Event(item) => item.fmt(f),
}
}
}
impl ItemKind<'_> {
pub fn name(&self) -> Option<Ident> {
match self {
Self::Pragma(_) | Self::Import(_) | Self::Using(_) => None,
Self::Contract(item) => Some(item.name),
Self::Function(item) => item.header.name,
Self::Variable(item) => item.name,
Self::Struct(item) => Some(item.name),
Self::Enum(item) => Some(item.name),
Self::Udvt(item) => Some(item.name),
Self::Error(item) => Some(item.name),
Self::Event(item) => Some(item.name),
}
}
pub fn description(&self) -> &'static str {
match self {
Self::Pragma(_) => "pragma directive",
Self::Import(_) => "import directive",
Self::Using(_) => "using directive",
Self::Contract(_) => "contract definition",
Self::Function(_) => "function definition",
Self::Variable(_) => "variable definition",
Self::Struct(_) => "struct definition",
Self::Enum(_) => "enum definition",
Self::Udvt(_) => "user-defined value type definition",
Self::Error(_) => "error definition",
Self::Event(_) => "event definition",
}
}
pub fn is_allowed_in_contract(&self) -> bool {
match self {
Self::Pragma(_) => false,
Self::Import(_) => false,
Self::Using(_) => true,
Self::Contract(_) => false,
Self::Function(_) => true,
Self::Variable(_) => true,
Self::Struct(_) => true,
Self::Enum(_) => true,
Self::Udvt(_) => true,
Self::Error(_) => true,
Self::Event(_) => true,
}
}
}
#[derive(Debug)]
pub struct PragmaDirective<'ast> {
pub tokens: PragmaTokens<'ast>,
}
#[derive(Debug)]
pub enum PragmaTokens<'ast> {
Version(Ident, SemverReq<'ast>),
Custom(IdentOrStrLit, Option<IdentOrStrLit>),
Verbatim(BoxSlice<'ast, Token>),
}
impl PragmaTokens<'_> {
pub fn as_name_and_value(&self) -> Option<(&IdentOrStrLit, Option<&IdentOrStrLit>)> {
match self {
Self::Custom(name, value) => Some((name, value.as_ref())),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub enum IdentOrStrLit {
Ident(Ident),
StrLit(StrLit),
}
impl IdentOrStrLit {
pub fn value(&self) -> Symbol {
match self {
Self::Ident(ident) => ident.name,
Self::StrLit(str_lit) => str_lit.value,
}
}
pub fn as_str(&self) -> &str {
match self {
Self::Ident(ident) => ident.as_str(),
Self::StrLit(str_lit) => str_lit.value.as_str(),
}
}
pub fn span(&self) -> Span {
match self {
Self::Ident(ident) => ident.span,
Self::StrLit(str_lit) => str_lit.span,
}
}
}
#[derive(Debug)]
pub struct ImportDirective<'ast> {
pub path: StrLit,
pub items: ImportItems<'ast>,
}
impl ImportDirective<'_> {
pub fn source_alias(&self) -> Option<Ident> {
self.items.source_alias()
}
}
#[derive(Debug)]
pub enum ImportItems<'ast> {
Plain(Option<Ident>),
Aliases(BoxSlice<'ast, (Ident, Option<Ident>)>),
Glob(Ident),
}
impl ImportItems<'_> {
pub fn source_alias(&self) -> Option<Ident> {
match *self {
ImportItems::Plain(ident) => ident,
ImportItems::Aliases(_) => None,
ImportItems::Glob(ident) => Some(ident),
}
}
}
#[derive(Debug)]
pub struct UsingDirective<'ast> {
pub list: UsingList<'ast>,
pub ty: Option<Type<'ast>>,
pub global: bool,
}
#[derive(Debug)]
pub enum UsingList<'ast> {
Single(AstPath<'ast>),
Multiple(BoxSlice<'ast, (AstPath<'ast>, Option<UserDefinableOperator>)>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum UserDefinableOperator {
BitAnd,
BitNot,
BitOr,
BitXor,
Add,
Div,
Rem,
Mul,
Sub,
Eq,
Ge,
Gt,
Le,
Lt,
Ne,
}
impl UserDefinableOperator {
pub const fn from_unop(op: UnOpKind) -> Option<Self> {
Some(match op {
UnOpKind::Neg => Self::Sub,
UnOpKind::BitNot => Self::BitNot,
UnOpKind::PreInc
| UnOpKind::PreDec
| UnOpKind::Not
| UnOpKind::PostInc
| UnOpKind::PostDec => {
return None;
}
})
}
pub const fn from_binop(op: BinOpKind) -> Option<Self> {
Some(match op {
BinOpKind::BitAnd => Self::BitAnd,
BinOpKind::BitOr => Self::BitOr,
BinOpKind::BitXor => Self::BitXor,
BinOpKind::Add => Self::Add,
BinOpKind::Div => Self::Div,
BinOpKind::Rem => Self::Rem,
BinOpKind::Mul => Self::Mul,
BinOpKind::Sub => Self::Sub,
BinOpKind::Eq => Self::Eq,
BinOpKind::Ge => Self::Ge,
BinOpKind::Gt => Self::Gt,
BinOpKind::Le => Self::Le,
BinOpKind::Lt => Self::Lt,
BinOpKind::Ne => Self::Ne,
BinOpKind::Or
| BinOpKind::And
| BinOpKind::Shr
| BinOpKind::Shl
| BinOpKind::Sar
| BinOpKind::Pow => {
return None;
}
})
}
pub const fn to_op(self) -> Either<UnOpKind, BinOpKind> {
match self {
Self::BitAnd => Either::Right(BinOpKind::BitAnd),
Self::BitNot => Either::Left(UnOpKind::BitNot),
Self::BitOr => Either::Right(BinOpKind::BitOr),
Self::BitXor => Either::Right(BinOpKind::BitXor),
Self::Add => Either::Right(BinOpKind::Add),
Self::Div => Either::Right(BinOpKind::Div),
Self::Rem => Either::Right(BinOpKind::Rem),
Self::Mul => Either::Right(BinOpKind::Mul),
Self::Sub => Either::Right(BinOpKind::Sub),
Self::Eq => Either::Right(BinOpKind::Eq),
Self::Ge => Either::Right(BinOpKind::Ge),
Self::Gt => Either::Right(BinOpKind::Gt),
Self::Le => Either::Right(BinOpKind::Le),
Self::Lt => Either::Right(BinOpKind::Lt),
Self::Ne => Either::Right(BinOpKind::Ne),
}
}
pub const fn to_str(self) -> &'static str {
match self.to_op() {
Either::Left(unop) => unop.to_str(),
Either::Right(binop) => binop.to_str(),
}
}
}
#[derive(Debug)]
pub struct ItemContract<'ast> {
pub kind: ContractKind,
pub name: Ident,
pub layout: Option<StorageLayoutSpecifier<'ast>>,
pub bases: BoxSlice<'ast, Modifier<'ast>>,
pub body: BoxSlice<'ast, Item<'ast>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIs)]
pub enum ContractKind {
Contract,
AbstractContract,
Interface,
Library,
}
impl fmt::Display for ContractKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
impl ContractKind {
pub const fn to_str(self) -> &'static str {
match self {
Self::Contract => "contract",
Self::AbstractContract => "abstract contract",
Self::Interface => "interface",
Self::Library => "library",
}
}
}
#[derive(Debug)]
pub struct StorageLayoutSpecifier<'ast> {
pub span: Span,
pub slot: Box<'ast, Expr<'ast>>,
}
#[derive(Debug)]
pub struct ItemFunction<'ast> {
pub kind: FunctionKind,
pub header: FunctionHeader<'ast>,
pub body: Option<Block<'ast>>,
pub body_span: Span,
}
impl ItemFunction<'_> {
pub fn is_implemented(&self) -> bool {
self.body.is_some()
}
}
#[derive(Debug, Default)]
pub struct FunctionHeader<'ast> {
pub span: Span,
pub name: Option<Ident>,
pub parameters: ParameterList<'ast>,
pub visibility: Option<Spanned<Visibility>>,
pub state_mutability: Option<Spanned<StateMutability>>,
pub modifiers: BoxSlice<'ast, Modifier<'ast>>,
pub virtual_: Option<Span>,
pub override_: Option<Override<'ast>>,
pub returns: Option<ParameterList<'ast>>,
}
impl<'ast> FunctionHeader<'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 virtual_(&self) -> bool {
self.virtual_.is_some()
}
pub fn returns(&self) -> &[VariableDefinition<'ast>] {
self.returns.as_ref().map(|pl| &pl.vars[..]).unwrap_or(&[])
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIs)]
pub enum FunctionKind {
Constructor,
Function,
Fallback,
Receive,
Modifier,
}
impl fmt::Display for FunctionKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
impl FunctionKind {
pub const fn to_str(self) -> &'static str {
match self {
Self::Constructor => "constructor",
Self::Function => "function",
Self::Fallback => "fallback",
Self::Receive => "receive",
Self::Modifier => "modifier",
}
}
pub fn allowed_in_global(&self) -> bool {
self.is_ordinary()
}
pub fn is_ordinary(&self) -> bool {
matches!(self, Self::Function)
}
}
#[derive(Debug)]
pub struct Modifier<'ast> {
pub name: AstPath<'ast>,
pub arguments: CallArgs<'ast>,
}
impl Modifier<'_> {
pub fn span(&self) -> Span {
self.name.span().to(self.arguments.span)
}
}
#[derive(Debug)]
pub struct Override<'ast> {
pub span: Span,
pub paths: BoxSlice<'ast, AstPath<'ast>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIs)]
pub enum DataLocation {
Storage,
Transient,
Memory,
Calldata,
}
impl fmt::Display for DataLocation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
impl DataLocation {
pub const fn to_str(self) -> &'static str {
match self {
Self::Storage => "storage",
Self::Transient => "transient",
Self::Memory => "memory",
Self::Calldata => "calldata",
}
}
pub const fn opt_to_str(this: Option<Self>) -> &'static str {
match this {
Some(location) => location.to_str(),
None => "none",
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, EnumIs, PartialOrd, Ord)]
pub enum StateMutability {
Pure,
View,
Payable,
#[default]
NonPayable,
}
impl fmt::Display for StateMutability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
impl StateMutability {
pub const fn to_str(self) -> &'static str {
match self {
Self::Pure => "pure",
Self::View => "view",
Self::Payable => "payable",
Self::NonPayable => "nonpayable",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Visibility {
Private,
Internal,
Public,
External,
}
impl fmt::Display for Visibility {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.to_str().fmt(f)
}
}
impl Visibility {
pub const fn to_str(self) -> &'static str {
match self {
Self::Private => "private",
Self::Internal => "internal",
Self::Public => "public",
Self::External => "external",
}
}
}
#[derive(Debug)]
pub struct VariableDefinition<'ast> {
pub span: Span,
pub ty: Type<'ast>,
pub visibility: Option<Visibility>,
pub mutability: Option<VarMut>,
pub data_location: Option<DataLocation>,
pub override_: Option<Override<'ast>>,
pub indexed: bool,
pub name: Option<Ident>,
pub initializer: Option<Box<'ast, Expr<'ast>>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum VarMut {
Immutable,
Constant,
}
impl fmt::Display for VarMut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.to_str())
}
}
impl VarMut {
pub const fn to_str(self) -> &'static str {
match self {
Self::Immutable => "immutable",
Self::Constant => "constant",
}
}
pub const fn is_immutable(self) -> bool {
matches!(self, Self::Immutable)
}
pub const fn is_constant(self) -> bool {
matches!(self, Self::Constant)
}
}
#[derive(Debug)]
pub struct ItemStruct<'ast> {
pub name: Ident,
pub fields: BoxSlice<'ast, VariableDefinition<'ast>>,
}
#[derive(Debug)]
pub struct ItemEnum<'ast> {
pub name: Ident,
pub variants: BoxSlice<'ast, Ident>,
}
#[derive(Debug)]
pub struct ItemUdvt<'ast> {
pub name: Ident,
pub ty: Type<'ast>,
}
#[derive(Debug)]
pub struct ItemError<'ast> {
pub name: Ident,
pub parameters: ParameterList<'ast>,
}
#[derive(Debug)]
pub struct ItemEvent<'ast> {
pub name: Ident,
pub parameters: ParameterList<'ast>,
pub anonymous: bool,
}