use super::Builtin;
use crate::{
hir,
ty::{Gcx, Ty, TyFn, TyFnKind, TyKind},
};
use solar_ast::{DataLocation, ElementaryType, StateMutability as SM, Visibility};
use solar_data_structures::BumpExt;
use solar_interface::Symbol;
pub type MemberList<'gcx> = &'gcx [Member<'gcx>];
pub(crate) type MemberListOwned<'gcx> = Vec<Member<'gcx>>;
pub(crate) fn native_members<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberList<'gcx> {
let expected_ref = || panic!("native_members: type {ty:?} should be wrapped in Ref");
gcx.bump().alloc_vec(match ty.kind {
TyKind::Elementary(elementary_type) => match elementary_type {
ElementaryType::Address(false) => address(gcx).collect(),
ElementaryType::Address(true) => address_payable(gcx).collect(),
ElementaryType::Bool => Default::default(),
ElementaryType::String => Default::default(),
ElementaryType::Bytes => expected_ref(),
ElementaryType::Fixed(..) | ElementaryType::UFixed(..) => Default::default(),
ElementaryType::Int(_size) => Default::default(),
ElementaryType::UInt(_size) => Default::default(),
ElementaryType::FixedBytes(_size) => fixed_bytes(gcx),
},
TyKind::StringLiteral(_utf8, _size) => Default::default(),
TyKind::IntLiteral(..) => Default::default(),
TyKind::Ref(inner, loc) => reference(gcx, ty, inner, loc),
TyKind::DynArray(_ty) => expected_ref(),
TyKind::Array(_ty, _len) => expected_ref(),
TyKind::Slice(_ty) => Default::default(),
TyKind::Tuple(_tys) => Default::default(),
TyKind::Mapping(..) => Default::default(),
TyKind::Fn(f) => function(gcx, f),
TyKind::Contract(id) => contract(gcx, id),
TyKind::Super(_id) => Default::default(),
TyKind::Struct(_id) => expected_ref(),
TyKind::Enum(_id) => Default::default(),
TyKind::Udvt(_ty, _id) => Default::default(),
TyKind::Error(_tys, _id) => Member::of_builtins(gcx, [Builtin::FunctionSelector]),
TyKind::Event(_tys, id) => {
if gcx.hir.event(id).anonymous {
Default::default()
} else {
Member::of_builtins(gcx, [Builtin::EventSelector])
}
}
TyKind::Module(id) => gcx.symbol_resolver.source_scopes[id]
.iter()
.flat_map(|(name, decls)| {
decls.iter().map(move |decl| Member::new(name, gcx.type_of_res(decl.res)))
})
.collect(),
TyKind::BuiltinModule(builtin) => builtin
.members()
.unwrap_or_else(|| panic!("builtin module {builtin:?} has no inner builtins"))
.map(|b| Member::of_builtin(gcx, b))
.collect(),
TyKind::Variadic => Default::default(),
TyKind::Type(ty) => type_type(gcx, ty),
TyKind::Meta(ty) => meta(gcx, ty),
TyKind::Err(_guar) => Default::default(),
})
}
pub(crate) fn contract_type_members_in_context<'gcx>(
gcx: Gcx<'gcx>,
id: hir::ContractId,
current_contract: hir::ContractId,
) -> MemberList<'gcx> {
gcx.bump().alloc_vec(contract_type(gcx, id, Some(current_contract)))
}
#[derive(Clone, Copy, Debug)]
pub struct Member<'gcx> {
pub name: Symbol,
pub ty: Ty<'gcx>,
pub res: Option<hir::Res>,
pub attached: bool,
}
impl<'gcx> Member<'gcx> {
pub fn new(name: Symbol, ty: Ty<'gcx>) -> Self {
Self { name, ty, res: None, attached: false }
}
pub fn with_res(name: Symbol, ty: Ty<'gcx>, res: impl Into<hir::Res>) -> Self {
Self { name, ty, res: Some(res.into()), attached: false }
}
pub fn with_attached_function(name: Symbol, ty: Ty<'gcx>, function: hir::FunctionId) -> Self {
Self { name, ty, res: Some(hir::ItemId::from(function).into()), attached: true }
}
pub fn with_builtin(builtin: Builtin, ty: Ty<'gcx>) -> Self {
Self::with_res(builtin.name(), ty, builtin)
}
pub fn with_attached_builtin(builtin: Builtin, ty: Ty<'gcx>) -> Self {
Self { name: builtin.name(), ty, res: Some(builtin.into()), attached: true }
}
pub fn of_builtin(gcx: Gcx<'gcx>, builtin: Builtin) -> Self {
Self::with_builtin(builtin, builtin.ty(gcx))
}
pub fn of_builtins(
gcx: Gcx<'gcx>,
builtins: impl IntoIterator<Item = Builtin>,
) -> MemberListOwned<'gcx> {
Self::of_builtins_iter(gcx, builtins).collect()
}
pub fn of_builtins_iter(
gcx: Gcx<'gcx>,
builtins: impl IntoIterator<Item = Builtin>,
) -> impl Iterator<Item = Self> {
builtins.into_iter().map(move |builtin| Self::of_builtin(gcx, builtin))
}
}
fn address(gcx: Gcx<'_>) -> impl Iterator<Item = Member<'_>> {
Member::of_builtins_iter(
gcx,
[
Builtin::AddressBalance,
Builtin::AddressCode,
Builtin::AddressCodehash,
Builtin::AddressCall,
Builtin::AddressDelegatecall,
Builtin::AddressStaticcall,
],
)
}
fn address_payable(gcx: Gcx<'_>) -> impl Iterator<Item = Member<'_>> {
address(gcx).chain(Member::of_builtins_iter(
gcx,
[Builtin::AddressPayableTransfer, Builtin::AddressPayableSend],
))
}
fn fixed_bytes(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(gcx, [Builtin::FixedBytesLength])
}
pub(crate) fn contract(gcx: Gcx<'_>, id: hir::ContractId) -> MemberListOwned<'_> {
let c = gcx.hir.contract(id);
if c.kind.is_library() {
return MemberListOwned::default();
}
gcx.interface_functions(id)
.iter()
.map(|f| {
let id = hir::ItemId::from(f.id);
Member::with_res(
gcx.item_name(id).name,
f.ty.as_externally_callable_function(false, gcx),
id,
)
})
.collect()
}
fn function<'gcx>(gcx: Gcx<'gcx>, f: &'gcx TyFn<'gcx>) -> MemberListOwned<'gcx> {
let mut members = Vec::with_capacity(2);
if f.has_selector() {
members.push(Member::of_builtin(gcx, Builtin::FunctionSelector));
}
if f.has_address() {
members.push(Member::of_builtin(gcx, Builtin::FunctionAddress));
}
members
}
fn reference<'gcx>(
gcx: Gcx<'gcx>,
this: Ty<'gcx>,
inner: Ty<'gcx>,
loc: DataLocation,
) -> MemberListOwned<'gcx> {
match (&inner.kind, loc) {
(&TyKind::Struct(id), _) => {
let fields = gcx.hir.strukt(id).fields;
let tys = gcx.struct_field_types(id);
debug_assert_eq!(fields.len(), tys.len());
fields
.iter()
.zip(tys)
.map(|(&f, &ty)| Member::new(gcx.item_name(f).name, ty.with_loc_if_ref(gcx, loc)))
.collect()
}
(
TyKind::DynArray(_) | TyKind::Elementary(ElementaryType::Bytes),
DataLocation::Storage,
) => {
let inner = if let TyKind::DynArray(inner) = inner.kind {
inner.with_loc_if_ref(gcx, loc)
} else {
gcx.types.fixed_bytes(1)
};
vec![
Member::of_builtin(gcx, Builtin::ArrayLength),
Member::with_attached_builtin(
Builtin::ArrayPush0,
gcx.mk_builtin_fn(&[this], SM::NonPayable, &[inner]),
),
Member::with_attached_builtin(
Builtin::ArrayPush,
gcx.mk_builtin_fn(&[this, inner], SM::NonPayable, &[]),
),
Member::with_attached_builtin(
Builtin::ArrayPop,
gcx.mk_builtin_fn(&[this], SM::NonPayable, &[]),
),
]
}
(
TyKind::Array(..) | TyKind::DynArray(_) | TyKind::Elementary(ElementaryType::Bytes),
_,
) => array(gcx),
_ => Default::default(),
}
}
fn type_type<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberListOwned<'gcx> {
match ty.kind {
TyKind::Contract(id) => contract_type(gcx, id, None),
TyKind::Super(id) => super_type(gcx, id),
TyKind::Enum(id) => {
gcx.hir.enumm(id).variants.iter().map(|v| Member::new(v.name, ty)).collect()
}
TyKind::Udvt(inner, _id) => {
vec![
Member::with_builtin(
Builtin::UdvtWrap,
gcx.mk_builtin_fn(&[inner], SM::Pure, &[ty]),
),
Member::with_builtin(
Builtin::UdvtUnwrap,
gcx.mk_builtin_fn(&[ty], SM::Pure, &[inner]),
),
]
}
TyKind::Elementary(ElementaryType::String) => string_ty(gcx),
TyKind::Elementary(ElementaryType::Bytes) => bytes_ty(gcx),
_ => Default::default(),
}
}
fn contract_type(
gcx: Gcx<'_>,
id: hir::ContractId,
current_contract: Option<hir::ContractId>,
) -> MemberListOwned<'_> {
let contract = gcx.hir.contract(id);
let access = ContractTypeAccess::new(gcx, id, current_contract);
let mut members = Vec::new();
match access {
ContractTypeAccess::External => {
members.extend(gcx.interface_functions(id).iter().map(|f| {
let item = hir::ItemId::from(f.id);
let ty = declaration_function_ty(gcx, gcx.type_of_item(item));
Member::with_res(gcx.item_name(item).name, ty, item)
}));
}
ContractTypeAccess::Library | ContractTypeAccess::DerivingScope { .. } => {
members.extend(contract.functions().filter_map(|id| {
let ty = contract_type_own_function(gcx, id, access)?;
let item = hir::ItemId::from(id);
Some(Member::with_res(gcx.item_name(item).name, ty, item))
}));
}
}
members.extend(contract.items.iter().copied().filter_map(|item| {
if matches!(item, hir::ItemId::Function(_))
|| !contract_type_item_visible(gcx.hir.item(item), access)
{
return None;
}
Some(Member::with_res(gcx.item_name(item).name, gcx.type_of_res(item.into()), item))
}));
members
}
#[derive(Clone, Copy)]
enum ContractTypeAccess {
Library,
DerivingScope { same_contract: bool },
External,
}
impl ContractTypeAccess {
fn new(gcx: Gcx<'_>, id: hir::ContractId, current_contract: Option<hir::ContractId>) -> Self {
let contract = gcx.hir.contract(id);
if contract.kind.is_library() {
Self::Library
} else if let Some(current) = current_contract
&& gcx.hir.contract(current).linearized_bases.contains(&id)
{
Self::DerivingScope { same_contract: current == id }
} else {
Self::External
}
}
}
fn contract_type_own_function<'gcx>(
gcx: Gcx<'gcx>,
id: hir::FunctionId,
access: ContractTypeAccess,
) -> Option<Ty<'gcx>> {
let f = gcx.hir.function(id);
if !f.is_ordinary() {
return None;
}
match access {
ContractTypeAccess::Library if f.visibility >= Visibility::Internal => {
let ty = gcx.type_of_item(id.into());
Some(if f.visibility >= Visibility::Public {
ty.as_externally_callable_function(true, gcx)
} else {
ty
})
}
ContractTypeAccess::DerivingScope { same_contract }
if f.visibility > Visibility::Private && !f.is_getter() =>
{
let ty = gcx.type_of_item(id.into());
Some(
if matches!(f.visibility, Visibility::Internal | Visibility::Public)
&& f.body.is_some()
{
if !same_contract && f.visibility >= Visibility::Public {
internal_function_with_selector(gcx, ty)
} else {
ty
}
} else {
declaration_function_ty(gcx, ty)
},
)
}
_ => None,
}
}
fn declaration_function_ty<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
let TyKind::Fn(fn_ty) = ty.kind else { unreachable!() };
gcx.mk_ty_fn(TyFn {
kind: TyFnKind::Declaration,
parameters: fn_ty.parameters,
returns: fn_ty.returns,
state_mutability: fn_ty.state_mutability,
function_id: fn_ty.function_id,
attached: false,
})
}
fn internal_function_with_selector<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
let TyKind::Fn(fn_ty) = ty.kind else { unreachable!() };
gcx.mk_ty_fn(TyFn {
kind: TyFnKind::InternalWithSelector,
parameters: fn_ty.parameters,
returns: fn_ty.returns,
state_mutability: fn_ty.state_mutability,
function_id: fn_ty.function_id,
attached: false,
})
}
fn contract_type_item_visible(item: hir::Item<'_, '_>, access: ContractTypeAccess) -> bool {
match access {
ContractTypeAccess::Library => {
item.is_visible_as_library_member() || item.is_visible_via_contract_type_access()
}
ContractTypeAccess::DerivingScope { .. } => {
item.is_visible_via_contract_type_access()
|| (matches!(item, hir::Item::Variable(_))
&& item.visibility() > Visibility::Private)
}
ContractTypeAccess::External => item.is_visible_via_contract_type_access(),
}
}
fn super_type(gcx: Gcx<'_>, id: hir::ContractId) -> MemberListOwned<'_> {
let mut members = Vec::new();
for &base in gcx.hir.contract(id).linearized_bases.iter().skip(1) {
for function in gcx.hir.contract(base).functions() {
let f = gcx.hir.function(function);
if !f.is_ordinary()
|| f.visibility <= Visibility::Private
|| f.visibility == Visibility::External
|| f.body.is_none()
{
continue;
}
let item = hir::ItemId::from(function);
let name = gcx.item_name(item).name;
let params = gcx.item_parameter_types(item);
if members.iter().any(|member: &Member<'_>| match member.res {
Some(hir::Res::Item(item)) => {
member.name == name && gcx.item_parameter_types(item) == params
}
_ => false,
}) {
continue;
}
let ty = gcx.type_of_item(item);
let ty = if f.visibility >= Visibility::Public {
internal_function_with_selector(gcx, ty)
} else {
ty
};
members.push(Member::with_res(name, ty, item));
}
}
members
}
pub(crate) fn internal_function_members_in_context<'gcx>(
gcx: Gcx<'gcx>,
id: hir::FunctionId,
current_contract: hir::ContractId,
) -> MemberListOwned<'gcx> {
let function = gcx.hir.function(id);
let Some(defining_contract) = function.contract else {
return MemberListOwned::default();
};
if current_contract == defining_contract
|| !function.is_part_of_external_interface()
|| !gcx.hir.contract(current_contract).linearized_bases.contains(&defining_contract)
{
return MemberListOwned::default();
}
Member::of_builtins(gcx, [Builtin::FunctionSelector])
}
fn meta<'gcx>(gcx: Gcx<'gcx>, ty: Ty<'gcx>) -> MemberListOwned<'gcx> {
match ty.kind {
TyKind::Contract(id) => {
if gcx.hir.contract(id).can_be_deployed() {
type_contract(gcx)
} else {
type_interface(gcx)
}
}
TyKind::Elementary(ElementaryType::Int(_) | ElementaryType::UInt(_)) | TyKind::Enum(_) => {
vec![
Member::with_builtin(Builtin::TypeMin, ty),
Member::with_builtin(Builtin::TypeMax, ty),
]
}
_ => Default::default(),
}
}
fn array(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(gcx, [Builtin::ArrayLength])
}
fn string_ty(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(gcx, [Builtin::StringConcat])
}
fn bytes_ty(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(gcx, [Builtin::BytesConcat])
}
fn type_contract(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(
gcx,
[Builtin::ContractCreationCode, Builtin::ContractRuntimeCode, Builtin::ContractName],
)
}
fn type_interface(gcx: Gcx<'_>) -> MemberListOwned<'_> {
Member::of_builtins(gcx, [Builtin::InterfaceId, Builtin::ContractName])
}