use crate::{
Source, Sources, ast,
ast_lowering::SymbolResolver,
builtins::{Builtin, members},
hir::{self, Hir, SourceId},
};
use alloy_primitives::{B256, Selector, U256, keccak256};
use either::Either;
use solar_ast::{DataLocation, StateMutability, TypeSize, UserDefinableOperator, Visibility};
use solar_data_structures::{
BumpExt,
fmt::{from_fn, or_list},
map::{FxBuildHasher, FxHashMap, FxHashSet},
smallvec::SmallVec,
trustme,
};
use solar_interface::{
Ident, Session, Span, Symbol,
config::CompilerStage,
diagnostics::{DiagCtxt, ErrorGuaranteed},
source_map::{FileName, SourceFile},
};
use std::{
fmt,
hash::Hash,
ops::ControlFlow,
sync::{
Arc, OnceLock,
atomic::{AtomicUsize, Ordering},
},
};
use thread_local::ThreadLocal;
mod print;
pub use print::{TyAbiPrinter, TyAbiPrinterMode};
mod common;
pub use common::{CommonTypes, EachDataLoc};
mod interner;
use interner::Interner;
#[allow(clippy::module_inception)]
mod ty;
pub(crate) use ty::SameSourceFileLevelUserTypeError;
pub use ty::{Ty, TyConvertError, TyData, TyFlags, TyFn, TyFnKind, TyKind};
type FxOnceMap<K, V> = once_map::OnceMap<K, V, FxBuildHasher>;
type NatSpecContractKey = (Symbol, hir::SourceId);
type UsingDirectiveKey = usize;
#[derive(Clone, Copy, Debug)]
pub struct InterfaceFunction<'gcx> {
pub id: hir::FunctionId,
pub selector: Selector,
pub ty: Ty<'gcx>,
}
#[derive(Clone, Copy, Debug)]
pub struct InterfaceFunctions<'gcx> {
pub functions: &'gcx [InterfaceFunction<'gcx>],
pub inheritance_start: usize,
}
#[derive(Clone, Debug, Default)]
pub struct TypeckResults<'gcx> {
pub(crate) expr_types: FxHashMap<hir::ExprId, Ty<'gcx>>,
pub(crate) resolved_callees: FxHashMap<hir::ExprId, ResolvedCallee>,
pub(crate) resolved_members: FxHashMap<hir::ExprId, ResolvedMember>,
pub(crate) unsupported_udvt_operators: FxHashSet<hir::ExprId>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ResolvedCallee {
pub res: hir::Res,
pub attached: bool,
}
impl ResolvedCallee {
#[inline]
pub fn new(res: hir::Res, attached: bool) -> Self {
Self { res, attached }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ResolvedMember {
Res(hir::Res),
StructField { struct_id: hir::StructId, field_index: usize },
EnumVariant { enum_id: hir::EnumId, variant_index: usize },
}
#[derive(Clone, Copy, Debug)]
pub struct MemberCompletion<'gcx> {
pub member: members::Member<'gcx>,
pub resolved: Option<ResolvedMember>,
}
pub type CallableParamNames = SmallVec<[Option<Symbol>; 8]>;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum CallableParamSource {
Function {
id: hir::FunctionId,
skips_receiver: bool,
},
FunctionType(hir::VariableId),
Struct(hir::StructId),
Event(hir::EventId),
Error(hir::ErrorId),
}
#[derive(Clone, Copy, Debug)]
pub struct CallableSignature<'gcx> {
pub parameters: &'gcx [Ty<'gcx>],
pub returns: &'gcx [Ty<'gcx>],
pub param_source: Option<CallableParamSource>,
}
impl<'gcx> TypeckResults<'gcx> {
#[inline]
pub fn type_of_expr(&self, id: hir::ExprId) -> Option<Ty<'gcx>> {
self.expr_types.get(&id).copied()
}
#[inline]
pub fn resolved_callee(&self, id: hir::ExprId) -> Option<ResolvedCallee> {
self.resolved_callees.get(&id).copied()
}
#[inline]
pub fn resolved_member(&self, id: hir::ExprId) -> Option<ResolvedMember> {
self.resolved_members.get(&id).copied()
}
#[inline]
pub fn builtin_member(&self, id: hir::ExprId) -> Option<Builtin> {
match self.resolved_member(id)? {
ResolvedMember::Res(hir::Res::Builtin(builtin)) => Some(builtin),
_ => None,
}
}
#[inline]
pub fn builtin_callee(&self, id: hir::ExprId) -> Option<Builtin> {
match self.resolved_callee(id)?.res {
hir::Res::Builtin(builtin) => Some(builtin),
_ => None,
}
}
#[inline]
pub fn unsupported_udvt_operator(&self, id: hir::ExprId) -> bool {
self.unsupported_udvt_operators.contains(&id)
}
}
impl<'gcx> InterfaceFunctions<'gcx> {
pub fn all(&self) -> &'gcx [InterfaceFunction<'gcx>] {
self.functions
}
pub fn own(&self) -> &'gcx [InterfaceFunction<'gcx>] {
&self.functions[..self.inheritance_start]
}
pub fn inherited(&self) -> &'gcx [InterfaceFunction<'gcx>] {
&self.functions[self.inheritance_start..]
}
}
impl<'gcx> std::ops::Deref for InterfaceFunctions<'gcx> {
type Target = &'gcx [InterfaceFunction<'gcx>];
#[inline]
fn deref(&self) -> &Self::Target {
&self.functions
}
}
impl<'gcx> IntoIterator for InterfaceFunctions<'gcx> {
type Item = &'gcx InterfaceFunction<'gcx>;
type IntoIter = std::slice::Iter<'gcx, InterfaceFunction<'gcx>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.functions.iter()
}
}
#[derive(Clone, Copy, Debug)]
pub enum Recursiveness {
None,
Recursive,
Infinite(ErrorGuaranteed),
}
impl Recursiveness {
#[inline]
pub fn is_none(self) -> bool {
matches!(self, Self::None)
}
#[inline]
pub fn is_recursive(self) -> bool {
!self.is_none()
}
}
#[derive(Clone, Copy)]
#[cfg_attr(feature = "nightly", rustc_pass_by_value)]
pub struct Gcx<'gcx>(&'gcx GlobalCtxt<'gcx>);
impl<'gcx> std::ops::Deref for Gcx<'gcx> {
type Target = &'gcx GlobalCtxt<'gcx>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'gcx> fmt::Debug for Gcx<'gcx> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
#[repr(transparent)]
pub(crate) struct GcxMut<'gcx>(*mut GlobalCtxt<'gcx>);
impl<'gcx> GcxMut<'gcx> {
#[inline(always)]
pub(crate) fn new(gcx: &mut GlobalCtxt<'gcx>) -> Self {
Self(gcx)
}
#[inline(always)]
pub(crate) fn get(&self) -> Gcx<'gcx> {
unsafe { Gcx(&*self.0) }
}
#[inline(always)]
pub(crate) fn get_mut(&mut self) -> &'gcx mut GlobalCtxt<'gcx> {
unsafe { &mut *self.0 }
}
}
impl<'gcx> std::ops::Deref for GcxMut<'gcx> {
type Target = &'gcx mut GlobalCtxt<'gcx>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
unsafe { core::mem::transmute(self) }
}
}
impl<'gcx> std::ops::DerefMut for GcxMut<'gcx> {
#[inline(always)]
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { core::mem::transmute(self) }
}
}
#[cfg(test)]
fn _gcx_traits() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Gcx<'static>>();
}
struct AtomicCompilerStage(AtomicUsize);
impl AtomicCompilerStage {
fn new() -> Self {
Self(AtomicUsize::new(usize::MAX))
}
fn set(&self, stage: CompilerStage) {
self.0.store(stage as usize, Ordering::Relaxed);
}
fn get(&self) -> Option<CompilerStage> {
let stage = self.0.load(Ordering::Relaxed);
if stage == usize::MAX { None } else { Some(CompilerStage::from_repr(stage).unwrap()) }
}
}
pub struct GlobalCtxt<'gcx> {
pub sess: &'gcx Session,
pub sources: Sources<'gcx>,
pub(crate) symbol_resolver: SymbolResolver<'gcx>,
pub hir: Hir<'gcx>,
stage: AtomicCompilerStage,
pub types: CommonTypes<'gcx>,
typeck_results: OnceLock<TypeckResults<'gcx>>,
pub(crate) ast_arenas: ThreadLocal<ast::Arena>,
pub(crate) hir_arenas: ThreadLocal<hir::Arena>,
interner: Interner<'gcx>,
cache: Cache<'gcx>,
pub(crate) inherited_override_functions:
FxOnceMap<hir::ContractId, &'gcx crate::typeck::override_checker::InheritedFunctions<'gcx>>,
}
impl fmt::Debug for GlobalCtxt<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobalCtxt")
.field("stage", &self.stage.get())
.field("sess", self.sess)
.field("sources", &self.sources.len())
.finish_non_exhaustive()
}
}
impl<'gcx> GlobalCtxt<'gcx> {
pub(crate) fn new(sess: &'gcx Session) -> Self {
let interner = Interner::new();
let hir_arenas = ThreadLocal::<hir::Arena>::new();
Self {
sess,
sources: Sources::new(),
symbol_resolver: SymbolResolver::new(&sess.dcx),
hir: Hir::new(),
stage: AtomicCompilerStage::new(),
types: CommonTypes::new(
&interner,
unsafe { trustme::decouple_lt(&hir_arenas) }.get_or_default().bump(),
),
typeck_results: Default::default(),
ast_arenas: ThreadLocal::new(),
hir_arenas,
interner,
cache: Cache::default(),
inherited_override_functions: FxOnceMap::default(),
}
}
}
impl<'gcx> Gcx<'gcx> {
pub(crate) fn new(gcx: &'gcx GlobalCtxt<'gcx>) -> Self {
Self(gcx)
}
pub fn stage(&self) -> Option<CompilerStage> {
self.stage.get()
}
pub(crate) fn advance_stage(&self, to: CompilerStage) -> ControlFlow<()> {
let from = self.stage();
let result = self.advance_stage_(to);
trace!(?from, ?to, ?result, "advance stage");
result
}
fn advance_stage_(&self, to: CompilerStage) -> ControlFlow<()> {
let current = self.stage();
if to == CompilerStage::Parsing && current == Some(to) {
return ControlFlow::Continue(());
}
let next = CompilerStage::next_opt(current);
if next.is_none_or(|next| to != next) {
let current_s = match current {
Some(s) => s.to_str(),
None => "none",
};
let next_s = match next {
Some(s) => &format!("`{s}`"),
None => "none (current stage is the last)",
};
self.dcx()
.bug(format!(
"invalid compiler stage transition: cannot advance from `{current_s}` to `{to}`"
))
.note(format!("expected next stage: {next_s}"))
.note("stages must be advanced sequentially")
.emit();
}
if let Some(current) = current
&& self.sess.stop_after(current)
{
return ControlFlow::Break(());
}
self.stage.set(to);
ControlFlow::Continue(())
}
pub fn dcx(self) -> &'gcx DiagCtxt {
&self.sess.dcx
}
pub fn arena(self) -> &'gcx hir::Arena {
self.hir_arenas.get_or_default()
}
pub fn bump(self) -> &'gcx bumpalo::Bump {
self.arena().bump()
}
pub fn alloc<T>(self, value: T) -> &'gcx T {
self.bump().alloc(value)
}
pub fn mk_ty(self, kind: TyKind<'gcx>) -> Ty<'gcx> {
self.interner.intern_ty(self.bump(), kind)
}
pub fn mk_tys(self, tys: &[Ty<'gcx>]) -> &'gcx [Ty<'gcx>] {
self.interner.intern_tys(self.bump(), tys)
}
pub fn mk_ty_iter(self, tys: impl Iterator<Item = Ty<'gcx>>) -> &'gcx [Ty<'gcx>] {
self.interner.intern_ty_iter(self.bump(), tys)
}
pub fn mk_ty_tuple(self, tys: &'gcx [Ty<'gcx>]) -> Ty<'gcx> {
self.mk_ty(TyKind::Tuple(tys))
}
pub(crate) fn mk_item_tys<T: Into<hir::ItemId> + Copy>(self, ids: &[T]) -> &'gcx [Ty<'gcx>] {
self.mk_ty_iter(ids.iter().map(|&id| self.type_of_item(id.into())))
}
pub fn mk_ty_string_literal(self, s: &[u8]) -> Ty<'gcx> {
self.mk_ty(TyKind::StringLiteral(
std::str::from_utf8(s).is_ok(),
TypeSize::new_int_bits(s.len().min(32) as u16 * 8),
))
}
pub fn mk_ty_int_literal(self, negative: bool, bits: u64) -> Option<Ty<'gcx>> {
self.mk_ty_int_literal_with_fixed_bytes(negative, bits, None)
}
pub fn mk_ty_int_literal_with_fixed_bytes(
self,
negative: bool,
bits: u64,
compatible_fixed_bytes: Option<TypeSize>,
) -> Option<Ty<'gcx>> {
let bits = bits.max(1);
if bits > TypeSize::MAX as u64 {
return None;
}
Some(self.mk_ty(TyKind::IntLiteral(
negative,
TypeSize::new_literal_bits(bits as u16),
compatible_fixed_bytes,
)))
}
pub fn mk_ty_fn(self, ptr: TyFn<'gcx>) -> Ty<'gcx> {
self.mk_ty(TyKind::Fn(self.interner.intern_ty_fn(self.bump(), ptr)))
}
#[inline]
pub fn type_of_expr(self, id: hir::ExprId) -> Option<Ty<'gcx>> {
self.typeck_results.get()?.type_of_expr(id)
}
#[inline]
pub fn resolved_callee(self, id: hir::ExprId) -> Option<ResolvedCallee> {
self.typeck_results.get()?.resolved_callee(id)
}
#[inline]
pub fn resolved_member(self, id: hir::ExprId) -> Option<ResolvedMember> {
self.typeck_results.get()?.resolved_member(id)
}
#[inline]
pub fn builtin_member(self, id: hir::ExprId) -> Option<Builtin> {
self.typeck_results.get()?.builtin_member(id)
}
#[inline]
pub fn builtin_callee(self, id: hir::ExprId) -> Option<Builtin> {
self.typeck_results.get()?.builtin_callee(id)
}
#[inline]
pub fn unsupported_udvt_operator(self, id: hir::ExprId) -> bool {
self.typeck_results.get().is_some_and(|results| results.unsupported_udvt_operator(id))
}
#[inline]
pub fn has_typeck_results(self) -> bool {
self.typeck_results.get().is_some()
}
pub(crate) fn set_typeck_results(self, results: TypeckResults<'gcx>) {
if self.typeck_results.set(results).is_err() {
self.dcx().bug("typeck results are already initialized").emit();
}
}
pub fn mk_ty_variadic(self) -> Ty<'gcx> {
self.mk_ty(TyKind::Variadic)
}
pub fn mk_ty_fn_with_kind(
self,
kind: TyFnKind,
parameters: &[Ty<'gcx>],
state_mutability: StateMutability,
returns: &[Ty<'gcx>],
) -> Ty<'gcx> {
self.mk_ty_fn(TyFn {
kind,
parameters: self.mk_tys(parameters),
returns: self.mk_tys(returns),
state_mutability: fn_state_mutability(kind, state_mutability),
function_id: None,
attached: false,
})
}
pub(crate) fn mk_builtin_fn(
self,
parameters: &[Ty<'gcx>],
state_mutability: StateMutability,
returns: &[Ty<'gcx>],
) -> Ty<'gcx> {
self.mk_ty_fn_with_kind(TyFnKind::Internal, parameters, state_mutability, returns)
}
pub(crate) fn mk_yul_builtin_fn(self, parameters: usize, returns: usize) -> Ty<'gcx> {
let parameters = vec![self.types.uint(256); parameters];
let returns = vec![self.types.uint(256); returns];
self.mk_builtin_fn(¶meters, StateMutability::NonPayable, &returns)
}
pub(crate) fn mk_creation_fn(
self,
parameters: &[Ty<'gcx>],
state_mutability: StateMutability,
returns: &[Ty<'gcx>],
) -> Ty<'gcx> {
self.mk_ty_fn_with_kind(TyFnKind::Creation, parameters, state_mutability, returns)
}
pub(crate) fn mk_builtin_mod(self, builtin: Builtin) -> Ty<'gcx> {
self.mk_ty(TyKind::BuiltinModule(builtin))
}
pub fn mk_ty_misc_err(self) -> Ty<'gcx> {
if let Err(e) = self.dcx().has_errors() {
self.mk_ty_err(e)
} else {
self.dcx().bug("mk_ty_misc_err: no errors").emit()
}
}
#[inline]
pub fn mk_ty_err(self, guar: ErrorGuaranteed) -> Ty<'gcx> {
const { assert!(std::mem::size_of::<ErrorGuaranteed>() == 0) }
let _ = guar;
self.types.__err_do_not_use
}
pub fn get_file(self, name: impl Into<FileName>) -> Option<Arc<SourceFile>> {
self.sess.source_map().get_file(name)
}
pub fn get_ast_source(
self,
name: impl Into<FileName>,
) -> Option<(SourceId, &'gcx Source<'gcx>)> {
let file = self.get_file(name)?;
self.sources.get_file(&file)
}
pub fn get_hir_source(
self,
name: impl Into<FileName>,
) -> Option<(SourceId, &'gcx hir::Source<'gcx>)> {
let file = self.get_file(name)?;
self.hir.sources.iter_enumerated().find(|(_, source)| Arc::ptr_eq(&source.file, &file))
}
pub fn item_name(self, id: impl Into<hir::ItemId>) -> Ident {
let id = id.into();
self.item_name_opt(id).unwrap_or_else(|| panic!("item_name: missing name for item {id:?}"))
}
pub fn item_canonical_name(self, id: impl Into<hir::ItemId>) -> impl fmt::Display {
self.item_canonical_name_(id.into())
}
fn item_canonical_name_(self, id: hir::ItemId) -> impl fmt::Display {
let name = self.item_name(id);
let contract = self.hir.item(id).contract().map(|id| self.item_name(id));
from_fn(move |f| {
if let Some(contract) = contract {
write!(f, "{contract}.")?;
}
write!(f, "{name}")
})
}
pub fn contract_fully_qualified_name(
self,
id: hir::ContractId,
) -> impl fmt::Display + use<'gcx> {
from_fn(move |f| {
let c = self.hir.contract(id);
let source = self.hir.source(c.source);
write!(f, "{}:{}", source.file.name.display(), c.name)
})
}
pub fn item_fields(
self,
id: impl Into<hir::ItemId>,
) -> impl Iterator<Item = (Ty<'gcx>, hir::VariableId)> {
self.item_fields_(id.into())
}
fn item_fields_(self, id: hir::ItemId) -> impl Iterator<Item = (Ty<'gcx>, hir::VariableId)> {
let tys = if let hir::ItemId::Struct(id) = id {
self.struct_field_types(id)
} else {
self.item_parameter_types(id)
};
let params = self.item_parameters(id);
debug_assert_eq!(tys.len(), params.len());
std::iter::zip(tys.iter().copied(), params.iter().copied())
}
pub fn item_parameters(self, id: impl Into<hir::ItemId>) -> &'gcx [hir::VariableId] {
let id = id.into();
self.item_parameters_opt(id)
.unwrap_or_else(|| panic!("item_parameters: invalid item {id:?}"))
}
pub fn item_parameters_opt(
self,
id: impl Into<hir::ItemId>,
) -> Option<&'gcx [hir::VariableId]> {
self.hir.item(id).parameters()
}
pub fn item_parameter_types(self, id: impl Into<hir::ItemId>) -> &'gcx [Ty<'gcx>] {
let id = id.into();
self.item_parameter_types_opt(id)
.unwrap_or_else(|| panic!("item_parameter_types: invalid item {id:?}"))
}
pub fn item_parameter_types_opt(self, id: impl Into<hir::ItemId>) -> Option<&'gcx [Ty<'gcx>]> {
self.type_of_item(id.into()).parameters()
}
#[inline]
pub fn item_name_opt(self, id: impl Into<hir::ItemId>) -> Option<Ident> {
self.hir.item(id).name()
}
#[inline]
pub fn item_span(self, id: impl Into<hir::ItemId>) -> Span {
self.hir.item(id).span()
}
pub fn function_selector(self, id: impl Into<hir::ItemId>) -> Selector {
let id = id.into();
assert!(
matches!(id, hir::ItemId::Function(_) | hir::ItemId::Error(_)),
"function_selector: invalid item {id:?}"
);
self.item_selector(id)[..4].try_into().unwrap()
}
pub fn event_selector(self, id: hir::EventId) -> B256 {
self.item_selector(id.into())
}
pub fn type_of_hir_ty(self, ty: &hir::Type<'_>) -> Ty<'gcx> {
let kind = match ty.kind {
hir::TypeKind::Elementary(ty) => TyKind::Elementary(ty),
hir::TypeKind::Array(array) => {
let elem = self.type_of_hir_ty(&array.element);
match array.size {
Some(size) => match crate::eval::eval_array_len(self, size) {
Ok(size) => TyKind::Array(elem, size),
Err(guar) => TyKind::Array(self.mk_ty_err(guar), U256::from(1)),
},
None => TyKind::DynArray(elem),
}
}
hir::TypeKind::Function(f) => {
let kind = if f.visibility == Visibility::External {
TyFnKind::External
} else {
TyFnKind::Internal
};
return self.mk_ty_fn(TyFn {
kind,
parameters: self.mk_item_tys(f.parameters),
returns: self.mk_item_tys(f.returns),
state_mutability: fn_state_mutability(kind, f.state_mutability),
function_id: None,
attached: false,
});
}
hir::TypeKind::Mapping(mapping) => {
let key = self.type_of_hir_ty(&mapping.key);
let value = self.type_of_hir_ty(&mapping.value);
TyKind::Mapping(key, value)
}
hir::TypeKind::Custom(item) => return self.type_of_item_simple(item, ty.span),
hir::TypeKind::Err(guar) => return self.mk_ty_err(guar),
};
self.mk_ty(kind)
}
pub(crate) fn type_of_using_directive(
self,
using: &'gcx hir::UsingDirective<'gcx>,
) -> Option<Ty<'gcx>> {
self.type_of_using_directive_cached(using as *const _ as UsingDirectiveKey)
}
fn type_of_item_simple(self, id: hir::ItemId, span: Span) -> Ty<'gcx> {
match id {
hir::ItemId::Contract(_)
| hir::ItemId::Struct(_)
| hir::ItemId::Enum(_)
| hir::ItemId::Udvt(_) => self.type_of_item(id),
_ => {
let msg = "name has to refer to a valid user-defined type";
self.mk_ty_err(self.dcx().emit_err(span, msg))
}
}
}
pub fn type_of_res(self, res: hir::Res) -> Ty<'gcx> {
match res {
hir::Res::Item(id) => {
let ty = self.type_of_item(id);
if is_value_ns(id) { ty } else { self.mk_ty(TyKind::Type(ty)) }
}
hir::Res::Namespace(id) => self.mk_ty(TyKind::Module(id)),
hir::Res::Builtin(builtin) => builtin.ty(self),
hir::Res::Err(guar) => self.mk_ty_err(guar),
}
}
pub fn callable_signature_of_ty(self, ty: Ty<'gcx>) -> Option<CallableSignature<'gcx>> {
match ty.kind {
TyKind::Fn(function_ty) => Some(CallableSignature {
parameters: function_ty.parameters,
returns: function_ty.returns,
param_source: self.callable_param_source_for_fn(function_ty),
}),
TyKind::Event(parameters, id) => Some(CallableSignature {
parameters,
returns: Default::default(),
param_source: Some(CallableParamSource::Event(id)),
}),
TyKind::Error(parameters, id) => Some(CallableSignature {
parameters,
returns: Default::default(),
param_source: Some(CallableParamSource::Error(id)),
}),
TyKind::Type(ty) => self.struct_constructor_signature(ty),
TyKind::Err(_) => None,
_ => None,
}
}
pub fn callable_signature_of_member(
self,
receiver_ty: Ty<'gcx>,
member: &members::Member<'gcx>,
) -> Option<CallableSignature<'gcx>> {
let TyKind::Fn(function_ty) = member.ty.kind else { return None };
let (parameters, skips_receiver) = if member.attached {
let (&self_ty, parameters) = function_ty.parameters.split_first()?;
if !receiver_ty.convert_implicit_to(self_ty, self) {
return None;
}
(parameters, true)
} else {
(function_ty.parameters, false)
};
Some(CallableSignature {
parameters,
returns: function_ty.returns,
param_source: function_ty
.function_id
.map(|id| CallableParamSource::Function { id, skips_receiver }),
})
}
pub fn callable_param_names(self, source: CallableParamSource) -> CallableParamNames {
match source {
CallableParamSource::Function { id, skips_receiver } => {
let mut names = self.param_names(self.hir.function(id).parameters);
if skips_receiver {
debug_assert!(!names.is_empty());
names.remove(0);
}
names
}
CallableParamSource::FunctionType(id) => match self.hir.variable(id).ty.kind {
hir::TypeKind::Function(ty) => self.param_names(ty.parameters),
_ => Default::default(),
},
CallableParamSource::Struct(id) => self.param_names(self.hir.strukt(id).fields),
CallableParamSource::Event(id) => self.param_names(self.hir.event(id).parameters),
CallableParamSource::Error(id) => self.param_names(self.hir.error(id).parameters),
}
}
fn callable_param_source_for_fn(self, function_ty: &TyFn<'gcx>) -> Option<CallableParamSource> {
function_ty.function_id.map(|id| {
let declared_param_count = self.hir.function(id).parameters.len();
let visible_param_count = function_ty.parameters.len();
debug_assert!(
declared_param_count == visible_param_count
|| declared_param_count == visible_param_count + 1
);
CallableParamSource::Function {
id,
skips_receiver: declared_param_count == visible_param_count + 1,
}
})
}
fn struct_constructor_signature(self, ty: Ty<'gcx>) -> Option<CallableSignature<'gcx>> {
let TyKind::Struct(id) = ty.kind else { return None };
let parameters = self.mk_ty_iter(
self.struct_field_types(id)
.iter()
.map(|&field_ty| field_ty.with_loc_if_ref(self, DataLocation::Memory)),
);
let returns = self.mk_ty_iter(std::iter::once(ty.with_loc(self, DataLocation::Memory)));
Some(CallableSignature {
parameters,
returns,
param_source: Some(CallableParamSource::Struct(id)),
})
}
fn param_names(self, params: &[hir::VariableId]) -> CallableParamNames {
params.iter().map(|&id| self.hir.variable(id).name.map(|i| i.name)).collect()
}
pub fn type_of_lit(self, lit: &'gcx hir::Lit<'gcx>) -> Ty<'gcx> {
match &lit.kind {
solar_ast::LitKind::Str(_, s, _) => self.mk_ty_string_literal(s.as_byte_str()),
solar_ast::LitKind::Number(int) => {
let compatible_fixed_bytes = compatible_fixed_bytes_type(lit);
self.mk_ty_int_literal_with_fixed_bytes(
false,
int.bit_len() as _,
compatible_fixed_bytes,
)
.unwrap_or_else(|| {
self.mk_ty_err(
self.dcx().emit_err(lit.span, "integer literal is greater than 2**256"),
)
})
}
solar_ast::LitKind::Rational(_) => {
self.mk_ty_err(self.dcx().emit_err(lit.span, "rational literals are not supported"))
}
solar_ast::LitKind::Address(_) => self.types.address,
solar_ast::LitKind::Bool(_) => self.types.bool,
&solar_ast::LitKind::Err(guar) => self.mk_ty_err(guar),
}
}
pub fn members_of(
self,
ty: Ty<'gcx>,
source: hir::SourceId,
contract: Option<hir::ContractId>,
) -> impl Iterator<Item = members::Member<'gcx>> + 'gcx {
let native =
self.native_members_in_context(ty, contract).unwrap_or_else(|| self.native_members(ty));
let attached = self.attached_functions(ty, source, contract);
native.iter().copied().chain(attached)
}
pub fn member_completions_of(
self,
ty: Ty<'gcx>,
source: hir::SourceId,
contract: Option<hir::ContractId>,
) -> impl Iterator<Item = MemberCompletion<'gcx>> + 'gcx {
self.members_of(ty, source, contract).map(move |member| MemberCompletion {
resolved: self.resolve_member_target(ty, member.name, member.res),
member,
})
}
pub(crate) fn resolve_member_target(
self,
receiver_ty: Ty<'gcx>,
name: Symbol,
res: Option<hir::Res>,
) -> Option<ResolvedMember> {
if let Some(res) = res {
return Some(ResolvedMember::Res(res));
}
match receiver_ty.kind {
TyKind::Ref(inner, _) => {
let TyKind::Struct(struct_id) = inner.kind else { return None };
let field_index = self.struct_field_index(struct_id, name)?;
Some(ResolvedMember::StructField { struct_id, field_index })
}
TyKind::Struct(struct_id) => {
let field_index = self.struct_field_index(struct_id, name)?;
Some(ResolvedMember::StructField { struct_id, field_index })
}
TyKind::Type(ty) => {
let TyKind::Enum(enum_id) = ty.kind else { return None };
let variant_index = self
.hir
.enumm(enum_id)
.variants
.iter()
.position(|variant| variant.name == name)?;
Some(ResolvedMember::EnumVariant { enum_id, variant_index })
}
_ => None,
}
}
fn struct_field_index(self, struct_id: hir::StructId, name: Symbol) -> Option<usize> {
self.hir.strukt(struct_id).fields.iter().position(|&field_id| {
self.hir.variable(field_id).name.is_some_and(|field| field.name == name)
})
}
fn native_members_in_context(
self,
ty: Ty<'gcx>,
current_contract: Option<hir::ContractId>,
) -> Option<members::MemberList<'gcx>> {
let current_contract = current_contract?;
match ty.kind {
TyKind::Type(ty) => {
let TyKind::Contract(id) = ty.kind else { return None };
let contract = self.hir.contract(id);
if contract.kind.is_library()
|| !self.hir.contract(current_contract).linearized_bases.contains(&id)
{
return None;
}
Some(self.contract_type_members_in_context((id, current_contract)))
}
TyKind::Fn(f) if f.kind == TyFnKind::Internal => {
let id = f.function_id?;
Some(self.internal_function_members_in_context((id, current_contract)))
}
_ => None,
}
}
pub(crate) fn for_each_user_operator(
self,
ty: Ty<'gcx>,
source: hir::SourceId,
contract: Option<hir::ContractId>,
op: UserDefinableOperator,
unary: bool,
f: &mut dyn FnMut(hir::FunctionId),
) {
let TyKind::Udvt(_, user_ty) = ty.peel_refs().kind else {
return;
};
let ty = self.type_of_item(user_ty.into());
let mut seen = FxHashSet::default();
self.for_each_using_directive_for_type(ty, source, contract, &mut |using| {
for entry in using.entries {
if entry.operator == Some(op)
&& let hir::UsingEntryKind::Functions(candidates) = entry.kind
{
for &function_id in candidates {
if let TyKind::Fn(function_ty) = self.type_of_item(function_id.into()).kind
&& function_ty.parameters.len() == if unary { 1 } else { 2 }
&& function_ty.parameters.first().copied() == Some(ty)
&& seen.insert(function_id)
{
f(function_id);
}
}
}
}
});
}
fn attached_functions(
self,
ty: Ty<'gcx>,
source: hir::SourceId,
contract: Option<hir::ContractId>,
) -> Vec<members::Member<'gcx>> {
let mut members = Vec::new();
let mut seen = FxHashSet::default();
self.for_each_using_directive_for_type(ty, source, contract, &mut |using| {
for entry in using.entries {
if entry.operator.is_some() {
continue;
}
match entry.kind {
hir::UsingEntryKind::Library(library) => {
for function in self.hir.contract(library).functions() {
let f = self.hir.function(function);
if !f.is_ordinary()
|| f.parameters.is_empty()
|| f.visibility == Visibility::Private
{
continue;
}
self.add_attached_function(ty, function, None, &mut seen, &mut members);
}
}
hir::UsingEntryKind::Functions(functions) => {
for &function in functions {
let name = entry.name.unwrap_or_else(|| self.item_name(function).name);
self.add_attached_function(
ty,
function,
Some(name),
&mut seen,
&mut members,
);
}
}
hir::UsingEntryKind::Err(_) => {}
}
}
});
members
}
fn add_attached_function(
self,
ty: Ty<'gcx>,
function: hir::FunctionId,
name: Option<Symbol>,
seen: &mut FxHashSet<(Symbol, hir::FunctionId)>,
members: &mut Vec<members::Member<'gcx>>,
) {
let function_item = self.hir.function(function);
let fn_ty = self.type_of_item(function.into());
let fn_ty =
if function_item.contract.is_some_and(|id| self.hir.contract(id).kind.is_library())
&& function_item.visibility >= Visibility::Public
{
fn_ty.as_externally_callable_function(true, self)
} else {
fn_ty
}
.as_attached_function(self);
if let TyKind::Fn(function_ty) = fn_ty.kind
&& let Some(&self_ty) = function_ty.parameters.first()
&& ty.convert_implicit_to(self_ty, self)
&& let name = name.unwrap_or_else(|| self.item_name(function).name)
&& seen.insert((name, function))
{
members.push(members::Member::with_attached_function(name, fn_ty, function));
}
}
fn for_each_using_directive_for_type(
self,
ty: Ty<'gcx>,
source: hir::SourceId,
contract: Option<hir::ContractId>,
f: &mut dyn FnMut(&'gcx hir::UsingDirective<'gcx>),
) {
let mut check = |usings: &'gcx [hir::UsingDirective<'gcx>], only_global: bool| {
for using in usings {
if self.using_directive_applies(using, ty, only_global) {
f(using);
}
}
};
if let Some(contract) = contract {
check(self.hir.contract(contract).usings, false);
}
check(self.hir.source(source).usings, false);
if let Some(type_source) = ty.item_source(self)
&& type_source != source
{
check(self.hir.source(type_source).usings, true);
}
}
fn using_directive_applies(
self,
using: &'gcx hir::UsingDirective<'gcx>,
ty: Ty<'gcx>,
only_global: bool,
) -> bool {
if only_global && !(using.global && using.ty.is_some()) {
return false;
}
let Some(using_ty) = self.type_of_using_directive(using) else {
return true;
};
let loc = ty.loc().unwrap_or(DataLocation::Storage);
using_directive_ty_matches(ty, using_ty.with_loc_if_ref(self, loc))
}
}
fn using_directive_ty_matches(ty: Ty<'_>, using_ty: Ty<'_>) -> bool {
if ty == using_ty {
return true;
}
if let (TyKind::Fn(a), TyKind::Fn(b)) = (ty.kind, using_ty.kind) {
return a.kind == b.kind
&& a.parameters == b.parameters
&& a.returns == b.returns
&& a.state_mutability == b.state_mutability
&& a.attached == b.attached;
}
false
}
fn compatible_fixed_bytes_type(lit: &hir::Lit<'_>) -> Option<TypeSize> {
let solar_ast::LitKind::Number(int) = lit.kind else { return None };
if int.is_zero() {
return Some(TypeSize::ZERO);
}
let hex = lit.symbol.as_str().strip_prefix("0x")?;
let digit_count = hex.bytes().filter(|&b| b != b'_').count();
if digit_count % 2 == 0 {
TypeSize::try_new_fb_bytes((digit_count / 2).try_into().ok()?)
} else {
None
}
}
fn fn_state_mutability(kind: TyFnKind, state_mutability: StateMutability) -> StateMutability {
if kind == TyFnKind::Internal && state_mutability == StateMutability::Payable {
StateMutability::NonPayable
} else {
state_mutability
}
}
macro_rules! cached {
($($(#[$attr:meta])* $vis:vis fn $name:ident($gcx:ident: _, $key:ident : $key_type:ty) -> $value:ty $imp:block)*) => {
#[derive(Default)]
struct Cache<'gcx> {
$(
$name: FxOnceMap<$key_type, $value>,
)*
}
impl<'gcx> Gcx<'gcx> {
$(
$(#[$attr])*
$vis fn $name(self, $key: $key_type) -> $value {
#[cfg(false)]
let _guard = log_cache_query(stringify!($name), &$key);
#[cfg(false)]
let mut hit = true;
let r = cache_insert(&self.cache.$name, $key, |&$key| {
#[cfg(false)]
{
hit = false;
}
let $gcx = self;
$imp
});
#[cfg(false)]
log_cache_query_result(&r, hit);
r
}
)*
}
};
}
cached! {
pub fn interface_id(gcx: _, id: hir::ContractId) -> Selector {
let kind = gcx.hir.contract(id).kind;
assert!(kind.is_interface(), "{kind} {id:?} is not an interface");
let selectors = gcx.interface_functions(id).own().iter().map(|f| f.selector);
selectors.fold(Selector::ZERO, std::ops::BitXor::bitxor)
}
pub fn interface_functions(gcx: _, id: hir::ContractId) -> InterfaceFunctions<'gcx> {
let c = gcx.hir.contract(id);
let mut inheritance_start = None;
let mut signatures_seen = FxHashSet::default();
let mut hash_collisions = FxHashMap::default();
let functions = c.linearized_bases.iter().flat_map(|&base| {
let b = gcx.hir.contract(base);
let functions =
b.functions().filter(|&f| gcx.hir.function(f).is_part_of_external_interface());
if base == id {
assert!(inheritance_start.is_none(), "duplicate self ID in linearized_bases");
inheritance_start = Some(functions.clone().count());
}
functions
}).filter_map(|f_id| {
let f = gcx.hir.function(f_id);
let TyKind::Fn(fn_ty) = gcx.type_of_item(f_id.into()).kind else { unreachable!() };
let ty = gcx
.mk_ty_fn(TyFn {
kind: TyFnKind::External,
parameters: fn_ty.parameters,
returns: fn_ty.returns,
state_mutability: f.state_mutability,
function_id: fn_ty.function_id,
attached: false,
})
.as_externally_callable_function(false, gcx);
let TyKind::Fn(ty_f) = ty.kind else { unreachable!() };
let mut result = Ok(());
for (var_id, ty) in f.variables().zip(ty_f.tys()) {
if let Err(guar) = ty.error_reported() {
result = Err(guar);
continue;
}
if !ty.can_be_exported(gcx) {
if c.kind.is_library() {
result = Err(ErrorGuaranteed::new_unchecked());
continue;
}
let kind = f.description();
let msg = if ty.has_mapping(gcx) {
format!("types containing mappings cannot be parameter or return types of public {kind}s")
} else if ty.is_recursive(gcx) {
format!("recursive types cannot be parameter or return types of public {kind}s")
} else if ty.has_internal_function() {
format!("types containing internal function pointers cannot be parameter or return types of public {kind}s")
} else {
format!("this type cannot be parameter or return type of a public {kind}")
};
let span = gcx.hir.variable(var_id).ty.span;
result = Err(gcx.dcx().emit_err(span, msg));
}
}
if result.is_err() {
return None;
}
let hash = gcx.item_selector(f_id.into());
let selector: Selector = hash[..4].try_into().unwrap();
if !signatures_seen.insert(hash) {
return None;
}
if let Some(prev) = hash_collisions.insert(selector, f_id) {
let f2 = gcx.hir.function(prev);
let msg = "function signature hash collision";
let full_note = format!(
"the function signatures `{}` and `{}` produce the same 4-byte selector `{selector}`",
gcx.item_signature(f_id.into()),
gcx.item_signature(prev.into()),
);
gcx.dcx().err(msg).span(c.name.span).span_note(f.span, "first function").span_note(f2.span, "second function").note(full_note).emit();
}
Some(InterfaceFunction { selector, id: f_id, ty })
});
let functions = gcx.bump().alloc_from_iter(functions);
trace!("{}.interfaceFunctions.len() = {}", gcx.contract_fully_qualified_name(id), functions.len());
let inheritance_start = inheritance_start.expect("linearized_bases did not contain self ID");
InterfaceFunctions { functions, inheritance_start }
}
pub(crate) fn base_override_functions(
gcx: _,
proxy: crate::typeck::override_checker::OverrideProxy
) -> &'gcx [crate::typeck::override_checker::OverrideProxy] {
crate::typeck::override_checker::base_override_functions(gcx, proxy)
}
pub fn natspec_doc_comments(gcx: _, id: hir::DocId) -> &'gcx [hir::NatSpecItem] {
crate::natspec::resolve_doc_comments(gcx, id)
}
pub(crate) fn natspec_contract_in_source(
gcx: _,
key: NatSpecContractKey
) -> Option<hir::ContractId> {
let (name, source_id) = key;
gcx.symbol_resolver.source_scopes[source_id]
.resolve(solar_interface::Ident { name, span: Span::DUMMY })
.and_then(|decls| {
decls.iter().find_map(|decl| match decl.res {
hir::Res::Item(hir::ItemId::Contract(id)) => Some(id),
_ => None,
})
})
}
pub fn item_signature(gcx: _, id: hir::ItemId) -> &'gcx str {
let name = gcx.item_name(id);
let tys = gcx.item_parameter_types(id);
let in_library =
gcx.hir.item(id).contract().is_some_and(|c| gcx.hir.contract(c).kind.is_library());
gcx.bump().alloc_str(&gcx.mk_abi_signature(name.as_str(), tys.iter().copied(), in_library))
}
pub(crate) fn item_selector(gcx: _, id: hir::ItemId) -> B256 {
keccak256(gcx.item_signature(id))
}
pub fn type_of_builtin(gcx: _, builtin: Builtin) -> Ty<'gcx> {
builtin.ty_impl(gcx)
}
fn type_of_using_directive_cached(gcx: _, key: UsingDirectiveKey) -> Option<Ty<'gcx>> {
let using = unsafe { &*(key as *const hir::UsingDirective<'gcx>) };
using.ty.as_ref().map(|ty| gcx.type_of_hir_ty(ty))
}
pub fn type_of_item(gcx: _, id: hir::ItemId) -> Ty<'gcx> {
let kind = match id {
hir::ItemId::Contract(id) => TyKind::Contract(id),
hir::ItemId::Function(id) => {
let f = gcx.hir.function(id);
return gcx.mk_ty_fn(TyFn {
kind: TyFnKind::Internal,
parameters: gcx.mk_item_tys(f.parameters),
returns: gcx.mk_item_tys(f.returns),
state_mutability: fn_state_mutability(TyFnKind::Internal, f.state_mutability),
function_id: Some(id),
attached: false,
});
}
hir::ItemId::Variable(id) => {
let var = gcx.hir.variable(id);
let ty = gcx.type_of_hir_ty(&var.ty);
return var_type(gcx, var, ty);
}
hir::ItemId::Struct(id) => TyKind::Struct(id),
hir::ItemId::Enum(id) => TyKind::Enum(id),
hir::ItemId::Udvt(id) => {
let udvt = gcx.hir.udvt(id);
if udvt.ty.kind.is_elementary()
&& let ty = gcx.type_of_hir_ty(&udvt.ty)
&& ty.is_value_type()
{
TyKind::Udvt(ty, id)
} else {
let msg = "the underlying type of UDVTs must be an elementary value type";
return gcx.mk_ty_err(gcx.dcx().emit_err(udvt.ty.span, msg));
}
}
hir::ItemId::Error(id) => {
TyKind::Error(gcx.mk_item_tys(gcx.hir.error(id).parameters), id)
}
hir::ItemId::Event(id) => {
TyKind::Event(gcx.mk_item_tys(gcx.hir.event(id).parameters), id)
}
};
gcx.mk_ty(kind)
}
pub fn struct_field_types(gcx: _, id: hir::StructId) -> &'gcx [Ty<'gcx>] {
gcx.mk_ty_iter(gcx.hir.strukt(id).fields.iter().map(|&f| gcx.type_of_item(f.into())))
}
pub fn struct_recursiveness(gcx: _, id: hir::StructId) -> Recursiveness {
use solar_data_structures::cycle::*;
let r = CycleDetector::detect(gcx, id, |gcx, cd, id| {
let s = gcx.hir.strukt(id);
if cd.depth() >= 256 {
let guar = gcx.dcx().emit_err(s.span, "struct is too deeply nested");
return CycleDetectorResult::Break(Either::Left(guar));
}
for &field_id in s.fields {
let field = gcx.hir.variable(field_id);
let mut check = |ty: &hir::Type<'_>, dynamic: bool| {
if let hir::TypeKind::Custom(hir::ItemId::Struct(other)) = ty.kind {
match cd.run(other) {
CycleDetectorResult::Continue => {}
CycleDetectorResult::Cycle(_) if dynamic => {
return CycleDetectorResult::Break(Either::Right(()));
}
r => return r,
}
}
CycleDetectorResult::Continue
};
let mut dynamic = false;
let mut ty = &field.ty;
while let hir::TypeKind::Array(array) = ty.kind {
if array.size.is_none() {
dynamic = true;
}
ty = &array.element;
}
cdr_try!(check(ty, dynamic));
if let ControlFlow::Break(r) = field.ty.visit(&gcx.hir, &mut |ty| check(ty, true).to_controlflow()) {
return r;
}
}
CycleDetectorResult::Continue
});
match r {
CycleDetectorResult::Continue => Recursiveness::None,
CycleDetectorResult::Break(Either::Left(guar)) => Recursiveness::Infinite(guar),
CycleDetectorResult::Break(Either::Right(())) => Recursiveness::Recursive,
CycleDetectorResult::Cycle(id) => Recursiveness::Infinite(
gcx.dcx().emit_err(gcx.item_span(id), "recursive struct definition")
),
}
}
fn native_members(gcx: _, ty: Ty<'gcx>) -> members::MemberList<'gcx> {
members::native_members(gcx, ty)
}
fn contract_type_members_in_context(
gcx: _,
key: (hir::ContractId, hir::ContractId)
) -> members::MemberList<'gcx> {
let (id, current_contract) = key;
members::contract_type_members_in_context(gcx, id, current_contract)
}
fn internal_function_members_in_context(
gcx: _,
key: (hir::FunctionId, hir::ContractId)
) -> members::MemberList<'gcx> {
let (id, current_contract) = key;
gcx.bump().alloc_vec(members::internal_function_members_in_context(gcx, id, current_contract))
}
}
fn var_type<'gcx>(gcx: Gcx<'gcx>, var: &'gcx hir::Variable<'gcx>, ty: Ty<'gcx>) -> Ty<'gcx> {
use hir::DataLocation::*;
let mut has_reference_or_mapping_type_slot = None;
let mut has_reference_or_mapping_type = || {
*has_reference_or_mapping_type_slot
.get_or_insert_with(|| ty.is_reference_type() || ty.has_mapping(gcx))
};
let mut func_vis = None;
let mut locs;
let allowed: &[_] = if var.is_state_variable() {
&[None, Some(Transient)]
} else if !has_reference_or_mapping_type() || var.is_event_or_error_parameter() {
&[None]
} else if var.is_callable_or_catch_parameter() {
locs = SmallVec::<[_; 3]>::new();
locs.push(Some(Memory));
let mut is_constructor_parameter = false;
if let Some(hir::ItemId::Function(f)) = var.parent {
let f = gcx.hir.function(f);
is_constructor_parameter = f.kind.is_constructor();
if !var.is_try_catch_parameter() && !is_constructor_parameter {
func_vis = Some(f.visibility);
}
if is_constructor_parameter
|| f.visibility <= hir::Visibility::Internal
|| f.contract.is_some_and(|c| gcx.hir.contract(c).kind.is_library())
{
locs.push(Some(Storage));
}
}
if !var.is_try_catch_parameter() && !is_constructor_parameter {
locs.push(Some(Calldata));
}
&locs
} else if var.is_local_variable() {
&[Some(Memory), Some(Storage), Some(Calldata)]
} else {
&[None]
};
let mut var_loc = var.data_location;
if !allowed.contains(&var_loc) {
if !ty.references_error() {
let msg = if !has_reference_or_mapping_type() {
"data location can only be specified for array, struct or mapping types".to_string()
} else if let Some(var_loc) = var_loc {
format!("invalid data location `{var_loc}`")
} else {
"expected data location".to_string()
};
let mut err = gcx.dcx().err(msg).span(var.span);
if has_reference_or_mapping_type() {
let note = format!(
"data location must be {expected} for {vis}{descr}{got}",
expected = or_list(
allowed.iter().map(|d| format!("`{}`", DataLocation::opt_to_str(*d)))
),
vis = if let Some(vis) = func_vis { format!("{vis} ") } else { String::new() },
descr = var.description(),
got = if let Some(var_loc) = var_loc {
format!(", but got `{var_loc}`")
} else {
String::new()
},
);
err = err.note(note);
}
err.emit();
}
var_loc = allowed[0];
}
let ty_loc = if var.is_event_or_error_parameter() || var.is_file_level_variable() {
Memory
} else if var.is_state_variable() {
let mut_specified = var.mutability.is_some();
match var_loc {
None => {
if mut_specified {
Memory
} else {
Storage
}
}
Some(Transient) => {
if mut_specified {
let msg = "transient cannot be used as data location for constant or immutable variables";
gcx.dcx().emit_err(var.span, msg);
}
if var.initializer.is_some() {
let msg =
"initialization of transient storage state variables is not supported";
gcx.dcx().emit_err(var.span, msg);
}
Transient
}
Some(_) => unreachable!(),
}
} else if var.is_struct_member() {
Storage
} else {
match var_loc {
Some(loc @ (Memory | Storage | Calldata)) => loc,
Some(Transient) => unimplemented!(),
None => {
debug_assert!(!has_reference_or_mapping_type(), "data location not properly set");
Memory
}
}
};
ty.with_loc_if_ref(gcx, ty_loc)
}
fn is_value_ns(id: hir::ItemId) -> bool {
matches!(
id,
hir::ItemId::Function(_)
| hir::ItemId::Variable(_)
| hir::ItemId::Error(_)
| hir::ItemId::Event(_)
)
}
#[inline]
fn cache_insert<K, V>(map: &FxOnceMap<K, V>, key: K, make_val: impl FnOnce(&K) -> V) -> V
where
K: Copy + Eq + Hash,
V: Copy,
{
map.map_insert(key, make_val, cache_insert_with_result)
}
#[inline]
fn cache_insert_with_result<K, V: Copy>(_: &K, v: &V) -> V {
*v
}
#[cfg(false)]
fn log_cache_query(name: &str, key: &dyn fmt::Debug) -> tracing::span::EnteredSpan {
let guard = trace_span!("query", %name, ?key).entered();
trace!("entered");
guard
}
#[cfg(false)]
fn log_cache_query_result(result: &dyn fmt::Debug, hit: bool) {
trace!(?result, hit);
}