use crate::builtins::Builtin;
use derive_more::derive::From;
use either::Either;
use rayon::prelude::*;
use solar_ast as ast;
use solar_data_structures::{BumpExt, index::IndexVec, newtype_index};
use solar_interface::{Ident, Span, diagnostics::ErrorGuaranteed, source_map::SourceFile};
use std::{fmt, ops::ControlFlow, sync::Arc};
use strum::EnumIs;
pub use ast::{
BinOp, BinOpKind, ContractKind, DataLocation, ElementaryType, FunctionKind, Lit,
StateMutability, UnOp, UnOpKind, VarMut, Visibility,
};
mod visit;
pub use visit::Visit;
pub struct Arena {
bump: bumpalo::Bump,
}
impl Arena {
pub fn new() -> Self {
Self { bump: bumpalo::Bump::new() }
}
pub fn bump(&self) -> &bumpalo::Bump {
&self.bump
}
pub fn bump_mut(&mut self) -> &mut bumpalo::Bump {
&mut self.bump
}
pub fn allocated_bytes(&self) -> usize {
self.bump.allocated_bytes()
}
pub fn used_bytes(&self) -> usize {
self.bump.used_bytes()
}
}
impl Default for Arena {
fn default() -> Self {
Self::new()
}
}
impl std::ops::Deref for Arena {
type Target = bumpalo::Bump;
#[inline]
fn deref(&self) -> &Self::Target {
&self.bump
}
}
#[derive(Debug)]
pub struct Hir<'hir> {
pub(crate) sources: IndexVec<SourceId, Source<'hir>>,
pub(crate) contracts: IndexVec<ContractId, Contract<'hir>>,
pub(crate) functions: IndexVec<FunctionId, Function<'hir>>,
pub(crate) structs: IndexVec<StructId, Struct<'hir>>,
pub(crate) enums: IndexVec<EnumId, Enum<'hir>>,
pub(crate) udvts: IndexVec<UdvtId, Udvt<'hir>>,
pub(crate) events: IndexVec<EventId, Event<'hir>>,
pub(crate) errors: IndexVec<ErrorId, Error<'hir>>,
pub(crate) variables: IndexVec<VariableId, Variable<'hir>>,
}
macro_rules! indexvec_methods {
($($singular:ident => $plural:ident, $id:ty => $type:ty;)*) => { paste::paste! {
$(
#[doc = "Returns the " $singular " associated with the given ID."]
#[inline]
#[cfg_attr(debug_assertions, track_caller)]
pub fn $singular(&self, id: $id) -> &$type {
if cfg!(debug_assertions) {
&self.$plural[id]
} else {
unsafe { self.$plural.raw.get_unchecked(id.index()) }
}
}
#[doc = "Returns an iterator over all of the " $singular " IDs."]
#[inline]
pub fn [<$singular _ids>](&self) -> impl ExactSizeIterator<Item = $id> + Clone + use<> {
(0..self.$plural.len()).map(|id| unsafe { $id::from_usize_unchecked(id) })
}
#[doc = "Returns a parallel iterator over all of the " $singular " IDs."]
#[inline]
pub fn [<par_ $singular _ids>](&self) -> impl IndexedParallelIterator<Item = $id> + use<> {
(0..self.$plural.len()).into_par_iter().map(|id| unsafe { $id::from_usize_unchecked(id) })
}
#[doc = "Returns an iterator over all of the " $singular " values."]
#[inline]
pub fn $plural(&self) -> impl ExactSizeIterator<Item = &$type> + Clone {
self.$plural.raw.iter()
}
#[doc = "Returns a parallel iterator over all of the " $singular " values."]
#[inline]
pub fn [<par_ $plural>](&self) -> impl IndexedParallelIterator<Item = &$type> {
self.$plural.raw.par_iter()
}
#[doc = "Returns an iterator over all of the " $singular " IDs and their associated values."]
#[inline]
pub fn [<$plural _enumerated>](&self) -> impl ExactSizeIterator<Item = ($id, &$type)> + Clone {
self.$plural().enumerate().map(|(i, v)| (unsafe { $id::from_usize_unchecked(i) }, v))
}
#[doc = "Returns an iterator over all of the " $singular " IDs and their associated values."]
#[inline]
pub fn [<par_ $plural _enumerated>](&self) -> impl IndexedParallelIterator<Item = ($id, &$type)> {
self.[<par_ $plural>]().enumerate().map(|(i, v)| (unsafe { $id::from_usize_unchecked(i) }, v))
}
)*
pub(crate) fn shrink_to_fit(&mut self) {
$(
self.$plural.shrink_to_fit();
)*
}
}};
}
impl<'hir> Hir<'hir> {
pub(crate) fn new() -> Self {
Self {
sources: IndexVec::new(),
contracts: IndexVec::new(),
functions: IndexVec::new(),
structs: IndexVec::new(),
enums: IndexVec::new(),
udvts: IndexVec::new(),
events: IndexVec::new(),
errors: IndexVec::new(),
variables: IndexVec::new(),
}
}
indexvec_methods! {
source => sources, SourceId => Source<'hir>;
contract => contracts, ContractId => Contract<'hir>;
function => functions, FunctionId => Function<'hir>;
strukt => structs, StructId => Struct<'hir>;
enumm => enums, EnumId => Enum<'hir>;
udvt => udvts, UdvtId => Udvt<'hir>;
event => events, EventId => Event<'hir>;
error => errors, ErrorId => Error<'hir>;
variable => variables, VariableId => Variable<'hir>;
}
#[inline]
pub fn item(&self, id: impl Into<ItemId>) -> Item<'_, 'hir> {
match id.into() {
ItemId::Contract(id) => Item::Contract(self.contract(id)),
ItemId::Function(id) => Item::Function(self.function(id)),
ItemId::Variable(id) => Item::Variable(self.variable(id)),
ItemId::Struct(id) => Item::Struct(self.strukt(id)),
ItemId::Enum(id) => Item::Enum(self.enumm(id)),
ItemId::Udvt(id) => Item::Udvt(self.udvt(id)),
ItemId::Error(id) => Item::Error(self.error(id)),
ItemId::Event(id) => Item::Event(self.event(id)),
}
}
pub fn item_ids(&self) -> impl Iterator<Item = ItemId> + Clone {
self.item_ids_vec().into_iter()
}
pub fn par_item_ids(&self) -> impl ParallelIterator<Item = ItemId> {
self.item_ids_vec().into_par_iter()
}
fn item_ids_vec(&self) -> Vec<ItemId> {
#[rustfmt::skip]
let len =
self.contracts.len()
+ self.functions.len()
+ self.variables.len()
+ self.structs.len()
+ self.enums.len()
+ self.udvts.len()
+ self.errors.len()
+ self.events.len();
let mut v = Vec::<ItemId>::with_capacity(len);
let mut items = v.spare_capacity_mut().iter_mut();
macro_rules! extend_unchecked {
($iter:expr) => {
for item in $iter {
unsafe { items.next().unwrap_unchecked().write(item) };
}
};
}
extend_unchecked!(self.contract_ids().map(ItemId::from));
extend_unchecked!(self.function_ids().map(ItemId::from));
extend_unchecked!(self.variable_ids().map(ItemId::from));
extend_unchecked!(self.strukt_ids().map(ItemId::from));
extend_unchecked!(self.enumm_ids().map(ItemId::from));
extend_unchecked!(self.udvt_ids().map(ItemId::from));
extend_unchecked!(self.error_ids().map(ItemId::from));
extend_unchecked!(self.event_ids().map(ItemId::from));
debug_assert!(items.next().is_none());
unsafe { v.set_len(len) };
debug_assert_eq!(v.len(), len);
debug_assert_eq!(v.capacity(), len);
v
}
pub fn contract_item_ids(
&self,
id: ContractId,
) -> impl Iterator<Item = ItemId> + Clone + use<'_> {
self.contract(id)
.linearized_bases
.iter()
.copied()
.flat_map(|base| self.contract(base).items.iter().copied())
}
pub fn contract_items(&self, id: ContractId) -> impl Iterator<Item = Item<'_, 'hir>> + Clone {
self.contract_item_ids(id).map(move |id| self.item(id))
}
}
newtype_index! {
pub struct SourceId;
pub struct ContractId;
pub struct FunctionId;
pub struct StructId;
pub struct EnumId;
pub struct UdvtId;
pub struct EventId;
pub struct ErrorId;
pub struct VariableId;
pub struct ExprId;
}
pub struct Source<'hir> {
pub file: Arc<SourceFile>,
pub imports: &'hir [(ast::ItemId, SourceId)],
pub items: &'hir [ItemId],
}
impl fmt::Debug for Source<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Source")
.field("file", &self.file.name)
.field("imports", &self.imports)
.field("items", &self.items)
.finish()
}
}
#[derive(Clone, Copy, Debug, EnumIs)]
pub enum Item<'a, 'hir> {
Contract(&'a Contract<'hir>),
Function(&'a Function<'hir>),
Struct(&'a Struct<'hir>),
Enum(&'a Enum<'hir>),
Udvt(&'a Udvt<'hir>),
Error(&'a Error<'hir>),
Event(&'a Event<'hir>),
Variable(&'a Variable<'hir>),
}
impl<'hir> Item<'_, 'hir> {
#[inline]
pub fn name(self) -> Option<Ident> {
match self {
Item::Contract(c) => Some(c.name),
Item::Function(f) => f.name,
Item::Struct(s) => Some(s.name),
Item::Enum(e) => Some(e.name),
Item::Udvt(u) => Some(u.name),
Item::Error(e) => Some(e.name),
Item::Event(e) => Some(e.name),
Item::Variable(v) => v.name,
}
}
#[inline]
pub fn description(self) -> &'static str {
match self {
Item::Contract(c) => c.description(),
Item::Function(f) => f.description(),
Item::Struct(_) => "struct",
Item::Enum(_) => "enum",
Item::Udvt(_) => "UDVT",
Item::Error(_) => "error",
Item::Event(_) => "event",
Item::Variable(_) => "variable",
}
}
#[inline]
pub fn span(self) -> Span {
match self {
Item::Contract(c) => c.span,
Item::Function(f) => f.span,
Item::Struct(s) => s.span,
Item::Enum(e) => e.span,
Item::Udvt(u) => u.span,
Item::Error(e) => e.span,
Item::Event(e) => e.span,
Item::Variable(v) => v.span,
}
}
#[inline]
pub fn contract(self) -> Option<ContractId> {
match self {
Item::Contract(_) => None,
Item::Function(f) => f.contract,
Item::Struct(s) => s.contract,
Item::Enum(e) => e.contract,
Item::Udvt(u) => u.contract,
Item::Error(e) => e.contract,
Item::Event(e) => e.contract,
Item::Variable(v) => v.contract,
}
}
#[inline]
pub fn source(self) -> SourceId {
match self {
Item::Contract(c) => c.source,
Item::Function(f) => f.source,
Item::Struct(s) => s.source,
Item::Enum(e) => e.source,
Item::Udvt(u) => u.source,
Item::Error(e) => e.source,
Item::Event(e) => e.source,
Item::Variable(v) => v.source,
}
}
#[inline]
pub fn parameters(self) -> Option<&'hir [VariableId]> {
Some(match self {
Item::Struct(s) => s.fields,
Item::Function(f) => f.parameters,
Item::Event(e) => e.parameters,
Item::Error(e) => e.parameters,
_ => return None,
})
}
#[inline]
pub fn is_visible_in_derived_contracts(self) -> bool {
self.is_visible_in_contract() && self.visibility() >= Visibility::Internal
}
#[inline]
pub fn is_visible_in_contract(self) -> bool {
(if let Item::Function(f) = self {
matches!(f.kind, FunctionKind::Function | FunctionKind::Modifier)
} else {
true
}) && self.visibility() != Visibility::External
}
#[inline]
pub fn is_public(&self) -> bool {
self.visibility() >= Visibility::Public
}
#[inline]
pub fn visibility(self) -> Visibility {
match self {
Item::Variable(v) => v.visibility.unwrap_or(Visibility::Internal),
Item::Contract(_)
| Item::Function(_)
| Item::Struct(_)
| Item::Enum(_)
| Item::Udvt(_)
| Item::Error(_)
| Item::Event(_) => Visibility::Public,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, From, EnumIs)]
pub enum ItemId {
Contract(ContractId),
Function(FunctionId),
Variable(VariableId),
Struct(StructId),
Enum(EnumId),
Udvt(UdvtId),
Error(ErrorId),
Event(EventId),
}
impl fmt::Debug for ItemId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("ItemId::")?;
match self {
Self::Contract(id) => id.fmt(f),
Self::Function(id) => id.fmt(f),
Self::Variable(id) => id.fmt(f),
Self::Struct(id) => id.fmt(f),
Self::Enum(id) => id.fmt(f),
Self::Udvt(id) => id.fmt(f),
Self::Error(id) => id.fmt(f),
Self::Event(id) => id.fmt(f),
}
}
}
impl ItemId {
pub fn description(&self) -> &'static str {
match self {
Self::Contract(_) => "contract",
Self::Function(_) => "function",
Self::Variable(_) => "variable",
Self::Struct(_) => "struct",
Self::Enum(_) => "enum",
Self::Udvt(_) => "UDVT",
Self::Error(_) => "error",
Self::Event(_) => "event",
}
}
#[inline]
pub fn matches(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
pub fn as_contract(&self) -> Option<ContractId> {
if let Self::Contract(v) = *self { Some(v) } else { None }
}
pub fn as_function(&self) -> Option<FunctionId> {
if let Self::Function(v) = *self { Some(v) } else { None }
}
pub fn as_struct(&self) -> Option<StructId> {
if let Self::Struct(v) = *self { Some(v) } else { None }
}
pub fn as_variable(&self) -> Option<VariableId> {
if let Self::Variable(v) = *self { Some(v) } else { None }
}
}
#[derive(Debug)]
pub struct Contract<'hir> {
pub source: SourceId,
pub span: Span,
pub name: Ident,
pub kind: ContractKind,
pub bases: &'hir [ContractId],
pub bases_args: &'hir [Modifier<'hir>],
pub linearized_bases: &'hir [ContractId],
pub linearized_bases_args: &'hir [Option<&'hir Modifier<'hir>>],
pub ctor: Option<FunctionId>,
pub fallback: Option<FunctionId>,
pub receive: Option<FunctionId>,
pub items: &'hir [ItemId],
}
impl Contract<'_> {
pub fn linearization_failed(&self) -> bool {
self.linearized_bases.is_empty()
|| (!self.bases.is_empty() && self.linearized_bases.len() == 1)
}
pub fn functions(&self) -> impl Iterator<Item = FunctionId> + Clone + use<'_> {
self.items.iter().filter_map(ItemId::as_function)
}
pub fn all_functions(&self) -> impl Iterator<Item = FunctionId> + Clone + use<'_> {
self.functions().chain(self.ctor).chain(self.fallback).chain(self.receive)
}
pub fn variables(&self) -> impl Iterator<Item = VariableId> + Clone + use<'_> {
self.items.iter().filter_map(ItemId::as_variable)
}
pub fn can_be_deployed(&self) -> bool {
matches!(self.kind, ContractKind::Contract | ContractKind::Library)
}
pub fn is_abstract(&self) -> bool {
self.kind.is_abstract_contract()
}
pub fn description(&self) -> &'static str {
self.kind.to_str()
}
}
#[derive(Clone, Copy, Debug)]
pub struct Modifier<'hir> {
pub span: Span,
pub id: ItemId,
pub args: CallArgs<'hir>,
}
#[derive(Debug)]
pub struct Function<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Option<Ident>,
pub kind: FunctionKind,
pub visibility: Visibility,
pub state_mutability: StateMutability,
pub modifiers: &'hir [Modifier<'hir>],
pub marked_virtual: bool,
pub virtual_: bool,
pub override_: bool,
pub overrides: &'hir [ContractId],
pub parameters: &'hir [VariableId],
pub returns: &'hir [VariableId],
pub body: Option<Block<'hir>>,
pub body_span: Span,
pub gettee: Option<VariableId>,
}
impl Function<'_> {
pub fn keyword_span(&self) -> Span {
self.span.with_hi(self.span.lo() + self.kind.to_str().len() as u32)
}
pub fn is_free(&self) -> bool {
self.contract.is_none()
}
pub fn is_ordinary(&self) -> bool {
self.kind.is_ordinary()
}
pub fn is_getter(&self) -> bool {
self.gettee.is_some()
}
pub fn is_part_of_external_interface(&self) -> bool {
self.is_ordinary() && self.visibility >= Visibility::Public
}
pub fn is_special(&self) -> bool {
matches!(self.kind, FunctionKind::Receive | FunctionKind::Fallback)
}
pub fn is_constructor(&self) -> bool {
matches!(self.kind, FunctionKind::Constructor)
}
pub fn mutates_state(&self) -> bool {
self.state_mutability >= StateMutability::Payable
}
pub fn variables(&self) -> impl DoubleEndedIterator<Item = VariableId> + Clone + use<'_> {
self.parameters.iter().copied().chain(self.returns.iter().copied())
}
pub fn description(&self) -> &'static str {
if self.is_getter() { "getter function" } else { self.kind.to_str() }
}
}
#[derive(Debug)]
pub struct Struct<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Ident,
pub fields: &'hir [VariableId],
}
#[derive(Debug)]
pub struct Enum<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Ident,
pub variants: &'hir [Ident],
}
#[derive(Debug)]
pub struct Udvt<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Ident,
pub ty: Type<'hir>,
}
#[derive(Debug)]
pub struct Event<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Ident,
pub anonymous: bool,
pub parameters: &'hir [VariableId],
}
#[derive(Debug)]
pub struct Error<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub span: Span,
pub name: Ident,
pub parameters: &'hir [VariableId],
}
#[derive(Debug)]
pub struct Variable<'hir> {
pub source: SourceId,
pub contract: Option<ContractId>,
pub function: Option<FunctionId>,
pub span: Span,
pub kind: VarKind,
pub ty: Type<'hir>,
pub name: Option<Ident>,
pub visibility: Option<Visibility>,
pub mutability: Option<VarMut>,
pub data_location: Option<DataLocation>,
pub override_: bool,
pub overrides: &'hir [ContractId],
pub indexed: bool,
pub initializer: Option<&'hir Expr<'hir>>,
pub getter: Option<FunctionId>,
}
impl<'hir> Variable<'hir> {
pub fn new(source: SourceId, ty: Type<'hir>, name: Option<Ident>, kind: VarKind) -> Self {
Self {
source,
contract: None,
function: None,
span: Span::DUMMY,
kind,
ty,
name,
visibility: None,
mutability: None,
data_location: None,
override_: false,
overrides: &[],
indexed: false,
initializer: None,
getter: None,
}
}
pub fn new_stmt(
source: SourceId,
contract: ContractId,
function: FunctionId,
ty: Type<'hir>,
name: Ident,
) -> Self {
Self {
contract: Some(contract),
function: Some(function),
..Self::new(source, ty, Some(name), VarKind::Statement)
}
}
pub fn description(&self) -> &'static str {
self.kind.to_str()
}
pub fn is_constant(&self) -> bool {
self.mutability == Some(VarMut::Constant)
}
pub fn is_immutable(&self) -> bool {
self.mutability == Some(VarMut::Immutable)
}
pub fn is_l_value(&self) -> bool {
!self.is_constant()
}
pub fn is_struct_member(&self) -> bool {
matches!(self.kind, VarKind::Struct)
}
pub fn is_event_or_error_parameter(&self) -> bool {
matches!(self.kind, VarKind::Event | VarKind::Error)
}
pub fn is_local_variable(&self) -> bool {
matches!(
self.kind,
VarKind::FunctionTyParam
| VarKind::FunctionTyReturn
| VarKind::Event
| VarKind::Error
| VarKind::FunctionParam
| VarKind::FunctionReturn
| VarKind::Statement
| VarKind::TryCatch
)
}
pub fn is_callable_or_catch_parameter(&self) -> bool {
matches!(
self.kind,
VarKind::Event
| VarKind::Error
| VarKind::FunctionParam
| VarKind::FunctionTyParam
| VarKind::FunctionReturn
| VarKind::FunctionTyReturn
| VarKind::TryCatch
)
}
pub fn is_local_or_return(&self) -> bool {
self.is_return_parameter()
|| (self.is_local_variable() && !self.is_callable_or_catch_parameter())
}
pub fn is_return_parameter(&self) -> bool {
matches!(self.kind, VarKind::FunctionReturn | VarKind::FunctionTyReturn)
}
pub fn is_try_catch_parameter(&self) -> bool {
matches!(self.kind, VarKind::TryCatch)
}
pub fn is_state_variable(&self) -> bool {
self.kind.is_state()
}
pub fn is_file_level_variable(&self) -> bool {
matches!(self.kind, VarKind::Global)
}
pub fn is_public(&self) -> bool {
self.visibility >= Some(Visibility::Public)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIs)]
pub enum VarKind {
Global,
State,
Struct,
Event,
Error,
FunctionParam,
FunctionReturn,
FunctionTyParam,
FunctionTyReturn,
Statement,
TryCatch,
}
impl VarKind {
pub fn to_str(self) -> &'static str {
match self {
Self::Global => "file-level variable",
Self::State => "state variable",
Self::Struct => "struct field",
Self::Event => "event parameter",
Self::Error => "error parameter",
Self::FunctionParam | Self::FunctionTyParam => "function parameter",
Self::FunctionReturn | Self::FunctionTyReturn => "function return parameter",
Self::Statement => "variable",
Self::TryCatch => "try/catch clause",
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Block<'hir> {
pub span: Span,
pub stmts: &'hir [Stmt<'hir>],
}
impl<'hir> std::ops::Deref for Block<'hir> {
type Target = [Stmt<'hir>];
fn deref(&self) -> &Self::Target {
self.stmts
}
}
#[derive(Debug)]
pub struct Stmt<'hir> {
pub span: Span,
pub kind: StmtKind<'hir>,
}
#[derive(Debug)]
pub enum StmtKind<'hir> {
DeclSingle(VariableId),
DeclMulti(&'hir [Option<VariableId>], &'hir Expr<'hir>),
Block(Block<'hir>),
UncheckedBlock(Block<'hir>),
Emit(&'hir Expr<'hir>),
Revert(&'hir Expr<'hir>),
Return(Option<&'hir Expr<'hir>>),
Break,
Continue,
Loop(Block<'hir>, LoopSource),
If(&'hir Expr<'hir>, &'hir Stmt<'hir>, Option<&'hir Stmt<'hir>>),
Try(&'hir StmtTry<'hir>),
Expr(&'hir Expr<'hir>),
Placeholder,
Err(ErrorGuaranteed),
}
#[derive(Debug)]
pub struct StmtTry<'hir> {
pub expr: Expr<'hir>,
pub clauses: &'hir [TryCatchClause<'hir>],
}
#[derive(Debug)]
pub struct TryCatchClause<'hir> {
pub span: Span,
pub name: Option<Ident>,
pub args: &'hir [VariableId],
pub block: Block<'hir>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LoopSource {
For,
While,
DoWhile,
}
impl LoopSource {
pub fn name(self) -> &'static str {
match self {
Self::For => "for",
Self::While => "while",
Self::DoWhile => "do while",
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, From, EnumIs)]
pub enum Res {
Item(ItemId),
Namespace(SourceId),
Builtin(Builtin),
Err(ErrorGuaranteed),
}
impl fmt::Debug for Res {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Res::")?;
match self {
Self::Item(id) => write!(f, "Item({id:?})"),
Self::Namespace(id) => write!(f, "Namespace({id:?})"),
Self::Builtin(b) => write!(f, "Builtin({b:?})"),
Self::Err(_) => f.write_str("Err"),
}
}
}
macro_rules! impl_try_from {
($($t:ty => $pat:pat => $e:expr),* $(,)?) => {
$(
impl TryFrom<Res> for $t {
type Error = ();
fn try_from(decl: Res) -> Result<Self, ()> {
match decl {
$pat => $e,
_ => Err(()),
}
}
}
)*
};
}
impl_try_from!(
ItemId => Res::Item(id) => Ok(id),
ContractId => Res::Item(ItemId::Contract(id)) => Ok(id),
EventId => Res::Item(ItemId::Event(id)) => Ok(id),
ErrorId => Res::Item(ItemId::Error(id)) => Ok(id),
);
impl Res {
pub fn description(&self) -> &'static str {
match self {
Self::Item(item) => item.description(),
Self::Namespace(_) => "namespace",
Self::Builtin(_) => "builtin",
Self::Err(_) => "<error>",
}
}
pub fn matches(&self, other: &Self) -> bool {
match (self, other) {
(Self::Item(a), Self::Item(b)) => a.matches(b),
_ => std::mem::discriminant(self) == std::mem::discriminant(other),
}
}
pub fn as_variable(&self) -> Option<VariableId> {
if let Self::Item(id) = self { id.as_variable() } else { None }
}
}
#[derive(Debug)]
pub struct Expr<'hir> {
pub id: ExprId,
pub kind: ExprKind<'hir>,
pub span: Span,
}
impl Expr<'_> {
pub fn peel_parens(&self) -> &Self {
let mut expr = self;
while let ExprKind::Tuple([Some(inner)]) = expr.kind {
expr = inner;
}
expr
}
}
#[derive(Debug)]
pub enum ExprKind<'hir> {
Array(&'hir [Expr<'hir>]),
Assign(&'hir Expr<'hir>, Option<BinOp>, &'hir Expr<'hir>),
Binary(&'hir Expr<'hir>, BinOp, &'hir Expr<'hir>),
Call(&'hir Expr<'hir>, CallArgs<'hir>, Option<&'hir [NamedArg<'hir>]>),
Delete(&'hir Expr<'hir>),
Ident(&'hir [Res]),
Index(&'hir Expr<'hir>, Option<&'hir Expr<'hir>>),
Slice(&'hir Expr<'hir>, Option<&'hir Expr<'hir>>, Option<&'hir Expr<'hir>>),
Lit(&'hir Lit<'hir>),
Member(&'hir Expr<'hir>, Ident),
New(Type<'hir>),
Payable(&'hir Expr<'hir>),
Ternary(&'hir Expr<'hir>, &'hir Expr<'hir>, &'hir Expr<'hir>),
Tuple(&'hir [Option<&'hir Expr<'hir>>]),
TypeCall(Type<'hir>),
Type(Type<'hir>),
Unary(UnOp, &'hir Expr<'hir>),
Err(ErrorGuaranteed),
}
#[derive(Debug)]
pub struct NamedArg<'hir> {
pub name: Ident,
pub value: Expr<'hir>,
}
#[derive(Clone, Copy, Debug)]
pub struct CallArgs<'hir> {
pub span: Span,
pub kind: CallArgsKind<'hir>,
}
impl<'hir> Default for CallArgs<'hir> {
fn default() -> Self {
Self::empty(Span::DUMMY)
}
}
impl<'hir> CallArgs<'hir> {
pub fn empty(span: Span) -> Self {
Self { span, kind: CallArgsKind::empty() }
}
pub fn is_dummy(&self) -> bool {
self.span.lo() == self.span.hi()
}
pub fn len(&self) -> usize {
self.kind.len()
}
pub fn is_empty(&self) -> bool {
self.kind.is_empty()
}
pub fn exprs(
&self,
) -> impl ExactSizeIterator<Item = &Expr<'hir>> + DoubleEndedIterator + Clone {
self.kind.exprs()
}
}
#[derive(Clone, Copy, Debug)]
pub enum CallArgsKind<'hir> {
Unnamed(&'hir [Expr<'hir>]),
Named(&'hir [NamedArg<'hir>]),
}
impl Default for CallArgsKind<'_> {
fn default() -> Self {
Self::empty()
}
}
impl<'hir> CallArgsKind<'hir> {
pub fn empty() -> Self {
Self::Unnamed(Default::default())
}
pub fn len(&self) -> usize {
match self {
Self::Unnamed(exprs) => exprs.len(),
Self::Named(args) => args.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn exprs(
&self,
) -> impl ExactSizeIterator<Item = &Expr<'hir>> + DoubleEndedIterator + Clone {
match self {
Self::Unnamed(exprs) => Either::Left(exprs.iter()),
Self::Named(args) => Either::Right(args.iter().map(|arg| &arg.value)),
}
}
pub fn span(&self) -> Option<Span> {
if self.is_empty() {
return None;
}
Some(Span::join_first_last(self.exprs().map(|e| e.span)))
}
}
#[derive(Clone, Debug)]
pub struct Type<'hir> {
pub span: Span,
pub kind: TypeKind<'hir>,
}
impl<'hir> Type<'hir> {
pub const DUMMY: Self =
Self { span: Span::DUMMY, kind: TypeKind::Err(ErrorGuaranteed::new_unchecked()) };
pub fn is_dummy(&self) -> bool {
self.span == Span::DUMMY && matches!(self.kind, TypeKind::Err(_))
}
pub fn visit<T>(
&self,
hir: &Hir<'hir>,
f: &mut impl FnMut(&Self) -> ControlFlow<T>,
) -> ControlFlow<T> {
f(self)?;
match self.kind {
TypeKind::Elementary(_) => ControlFlow::Continue(()),
TypeKind::Array(ty) => ty.element.visit(hir, f),
TypeKind::Function(ty) => {
for ¶m in ty.parameters {
hir.variable(param).ty.visit(hir, f)?;
}
for &ret in ty.returns {
hir.variable(ret).ty.visit(hir, f)?;
}
ControlFlow::Continue(())
}
TypeKind::Mapping(ty) => {
ty.key.visit(hir, f)?;
ty.value.visit(hir, f)
}
TypeKind::Custom(_) => ControlFlow::Continue(()),
TypeKind::Err(_) => ControlFlow::Continue(()),
}
}
}
#[derive(Clone, Debug)]
pub enum TypeKind<'hir> {
Elementary(ElementaryType),
Array(&'hir TypeArray<'hir>),
Function(&'hir TypeFunction<'hir>),
Mapping(&'hir TypeMapping<'hir>),
Custom(ItemId),
Err(ErrorGuaranteed),
}
impl TypeKind<'_> {
pub fn is_elementary(&self) -> bool {
matches!(self, Self::Elementary(_))
}
#[inline]
pub fn is_reference_type(&self) -> bool {
match self {
TypeKind::Elementary(t) => t.is_reference_type(),
TypeKind::Custom(ItemId::Struct(_)) | TypeKind::Array(_) => true,
_ => false,
}
}
}
#[derive(Debug)]
pub struct TypeArray<'hir> {
pub element: Type<'hir>,
pub size: Option<&'hir Expr<'hir>>,
}
#[derive(Debug)]
pub struct TypeFunction<'hir> {
pub parameters: &'hir [VariableId],
pub visibility: Visibility,
pub state_mutability: StateMutability,
pub returns: &'hir [VariableId],
}
#[derive(Debug)]
pub struct TypeMapping<'hir> {
pub key: Type<'hir>,
pub key_name: Option<Ident>,
pub value: Type<'hir>,
pub value_name: Option<Ident>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[cfg_attr(not(target_pointer_width = "64"), ignore = "64-bit only")]
#[cfg_attr(feature = "nightly", ignore = "stable only")]
fn sizes() {
use snapbox::{assert_data_eq, str};
#[track_caller]
fn assert_size<T>(size: impl snapbox::IntoData) {
assert_size_(std::mem::size_of::<T>(), size.into_data());
}
#[track_caller]
fn assert_size_(actual: usize, expected: snapbox::Data) {
assert_data_eq!(actual.to_string(), expected);
}
assert_size::<Hir<'_>>(str!["216"]);
assert_size::<Item<'_, '_>>(str!["16"]);
assert_size::<Contract<'_>>(str!["120"]);
assert_size::<Function<'_>>(str!["136"]);
assert_size::<Struct<'_>>(str!["48"]);
assert_size::<Enum<'_>>(str!["48"]);
assert_size::<Udvt<'_>>(str!["56"]);
assert_size::<Error<'_>>(str!["48"]);
assert_size::<Event<'_>>(str!["48"]);
assert_size::<Variable<'_>>(str!["96"]);
assert_size::<TypeKind<'_>>(str!["16"]);
assert_size::<Type<'_>>(str!["24"]);
assert_size::<ExprKind<'_>>(str!["56"]);
assert_size::<Expr<'_>>(str!["72"]);
assert_size::<StmtKind<'_>>(str!["32"]);
assert_size::<Stmt<'_>>(str!["40"]);
assert_size::<Block<'_>>(str!["24"]);
}
}