use crate::{builtins::Builtin, hir};
use alloy_primitives::U256;
use solar_ast as ast;
use solar_data_structures::{
BumpExt,
index::{Idx, IndexVec},
map::{FxHashMap, FxIndexMap, IndexEntry},
smallvec::SmallVec,
};
use solar_interface::{
Ident, Session, Span, Symbol,
diagnostics::{DiagCtxt, ErrorGuaranteed},
error_code, sym,
};
use std::fmt;
pub(crate) use crate::hir::Res;
impl super::LoweringContext<'_> {
#[instrument(level = "debug", skip_all)]
pub(super) fn collect_exports(&mut self) {
assert!(self.resolver.source_scopes.is_empty(), "exports already collected");
self.resolver.source_scopes = self
.hir
.sources()
.map(|source| {
let mut scope = Declarations::with_capacity(source.items.len());
for &item_id in source.items {
if let hir::ItemId::Function(id) = item_id
&& self.hir.function(id).is_yul
{
continue;
}
let item = self.hir.item(item_id);
if let Some(name) = item.name() {
let decl = Declaration { res: Res::Item(item_id), span: name.span };
let _ = self.declare_in(&mut scope, name.name, decl);
}
}
scope
})
.collect();
}
#[instrument(level = "debug", skip_all)]
pub(super) fn perform_imports(&mut self) {
for (source_id, source) in self.hir.sources_enumerated() {
for &(item_id, import_id) in source.imports {
let import_item = &self.sources[source_id].ast.as_ref().unwrap().items[item_id];
let ast::ItemKind::Import(import) = &import_item.kind else { unreachable!() };
let (source_scope, import_scope) = if source_id != import_id {
let (a, b) = super::get_two_mut_idx(
&mut self.resolver.source_scopes,
source_id,
import_id,
);
(a, Some(&*b))
} else {
(&mut self.resolver.source_scopes[source_id], None)
};
match import.items {
ast::ImportItems::Plain(_) | ast::ImportItems::Glob(_) => {
if let Some(alias) = import.items.source_alias() {
let _ = source_scope.declare_res(
self.sess,
&self.hir,
alias,
Res::Namespace(import_id),
);
} else if let Some(import_scope) = import_scope {
for (&name, decls) in &import_scope.declarations {
for decl in decls {
let mut decl = *decl;
decl.span = import_item.span;
let _ = source_scope.declare(self.sess, &self.hir, name, decl);
}
}
} else {
}
}
ast::ImportItems::Aliases(ref aliases) => {
for &(import, alias) in aliases.iter() {
let name = alias.unwrap_or(import);
let slot;
let resolved = if let Some(import_scope) = import_scope {
import_scope.resolve(import)
} else {
slot = source_scope.resolve_cloned(import);
slot.as_deref()
};
if let Some(resolved) = resolved {
debug_assert!(!resolved.is_empty());
for mut decl in resolved.iter().copied() {
decl.span = name.span;
let _ =
source_scope.declare(self.sess, &self.hir, name.name, decl);
}
} else {
let msg = format!(
"declaration `{import}` not found in {}",
self.sess
.source_map()
.filename_for_diagnostics(&source.file.name)
);
let guar = self.sess.dcx.emit_err(import.span, msg);
let _ = source_scope.declare_res(
self.sess,
&self.hir,
name,
Res::Err(guar),
);
}
}
}
}
}
}
}
#[instrument(level = "debug", skip_all)]
pub(super) fn collect_contract_declarations(&mut self) {
assert!(
self.resolver.contract_scopes.is_empty(),
"contract declarations already collected"
);
self.resolver.contract_scopes = self
.hir
.contracts()
.map(|contract| {
let mut scope = Declarations::with_capacity(contract.items.len() + 2);
let span = Span::DUMMY;
let this = Declaration { res: Res::Builtin(Builtin::This), span };
let _ = self.declare_in(&mut scope, sym::this, this);
if !contract.kind.is_library() {
let super_ = Declaration { res: Res::Builtin(Builtin::Super), span };
let _ = self.declare_in(&mut scope, sym::super_, super_);
}
for &item_id in contract.items {
if let hir::ItemId::Function(id) = item_id
&& self.hir.function(id).is_yul
{
continue;
}
if let Some(name) = self.hir.item(item_id).name() {
let _ = self.declare_kind_in(&mut scope, name, Res::Item(item_id));
}
}
scope
})
.collect();
}
#[instrument(level = "debug", skip_all)]
pub(super) fn resolve_base_contracts(&mut self) {
let mut scopes = SymbolResolverScopes::new();
for contract_id in self.hir.contract_ids() {
let item = self.hir_to_ast[&hir::ItemId::Contract(contract_id)];
let ast::ItemKind::Contract(ast_contract) = &item.kind else { unreachable!() };
if ast_contract.bases.is_empty() {
continue;
}
scopes.clear();
scopes.source = Some(self.hir.contract(contract_id).source);
let mut bases = SmallVec::<[_; 8]>::new();
for base in ast_contract.bases.iter() {
let name = &base.name;
let Ok(base_id) = self
.resolver
.resolve_path_as::<hir::ContractId>(&base.name, &scopes, "contract")
else {
continue;
};
if base_id == contract_id {
let msg = "contracts cannot inherit from themselves";
self.dcx().emit_err(name.span(), msg);
continue;
}
bases.push(base_id);
}
self.hir.contracts[contract_id].bases = self.arena.alloc_slice_copy(&bases);
}
}
#[instrument(level = "debug", skip_all)]
pub(super) fn assign_constructors(&mut self) {
for contract_id in self.hir.contract_ids() {
let mut ctor = None;
let mut fallback = None;
let mut receive = None;
for &base_id in self.hir.contract(contract_id).linearized_bases {
for function_id in self.hir.contract(base_id).functions() {
let func = self.hir.function(function_id);
let slot = match func.kind {
ast::FunctionKind::Constructor if base_id == contract_id => &mut ctor,
ast::FunctionKind::Fallback => &mut fallback,
ast::FunctionKind::Receive => &mut receive,
_ => continue,
};
if let Some(prev) = *slot {
if base_id != contract_id {
continue;
}
let msg = format!("{} function already declared", func.kind);
let note = "previous declaration here";
let prev_span = self.hir.function(prev).span;
self.dcx().emit_err_span_note(func.span, msg, prev_span, note);
} else {
*slot = Some(function_id);
}
}
}
let c = &mut self.hir.contracts[contract_id];
c.ctor = ctor;
c.fallback = fallback;
c.receive = receive;
}
}
}
#[rustfmt::skip]
macro_rules! mk_init_cx {
($cx:expr) => {
macro_rules! init_cx {
($e:expr) => {
init_cx!(@scopes $e.source, $e.contract, None)
};
(@scopes $source:expr, $contract:expr, $f:expr) => {
$cx.init($source, $contract, $f);
};
}
};
}
pub(super) struct ResolveContext<'gcx> {
pub(super) lcx: super::LoweringContext<'gcx>,
pub(super) scopes: SymbolResolverScopes,
function_id: Option<hir::FunctionId>,
yul_scopes: Vec<usize>,
yul_function_scope: Option<usize>,
}
impl<'gcx> std::ops::Deref for ResolveContext<'gcx> {
type Target = super::LoweringContext<'gcx>;
#[inline]
fn deref(&self) -> &Self::Target {
&self.lcx
}
}
impl std::ops::DerefMut for ResolveContext<'_> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.lcx
}
}
impl<'gcx> ResolveContext<'gcx> {
pub(super) fn new(lcx: super::LoweringContext<'gcx>) -> Self {
Self {
lcx,
scopes: SymbolResolverScopes::new(),
function_id: None,
yul_scopes: Vec::new(),
yul_function_scope: None,
}
}
fn init(
&mut self,
source: hir::SourceId,
contract: Option<hir::ContractId>,
function: Option<hir::FunctionId>,
) {
self.scopes.init(source, contract);
self.function_id = function;
}
#[instrument(level = "debug", skip_all)]
pub(super) fn resolve_symbols(&mut self) {
mk_init_cx!(self);
for id in self.hir.udvt_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Udvt(id)];
let ast::ItemKind::Udvt(ast_udvt) = &ast_item.kind else { unreachable!() };
let udvt = self.hir.udvt(id);
init_cx!(udvt);
self.hir.udvts[id].ty = self.lower_type(&ast_udvt.ty);
}
for id in self.hir.strukt_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Struct(id)];
let ast::ItemKind::Struct(ast_struct) = &ast_item.kind else { unreachable!() };
let strukt = self.hir.strukt(id);
init_cx!(strukt);
self.hir.structs[id].fields = self.lower_variables(
ast_struct.fields,
Some(hir::ItemId::Struct(id)),
hir::VarKind::Struct,
);
}
for id in self.hir.error_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Error(id)];
let ast::ItemKind::Error(ast_error) = &ast_item.kind else { unreachable!() };
let error = self.hir.error(id);
init_cx!(error);
self.hir.errors[id].parameters = self.lower_variables(
*ast_error.parameters,
Some(hir::ItemId::Error(id)),
hir::VarKind::Error,
);
}
for id in self.hir.event_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Event(id)];
let ast::ItemKind::Event(ast_event) = &ast_item.kind else { unreachable!() };
let event = self.hir.event(id);
init_cx!(event);
self.hir.events[id].parameters = self.lower_variables(
*ast_event.parameters,
Some(hir::ItemId::Event(id)),
hir::VarKind::Event,
);
}
let normal_vars = self.hir.variables.len();
for id in self.hir.variable_ids() {
self.resolve_var(id);
}
for id in self.hir.function_ids() {
let func = self.hir.function(id);
if func.is_getter() {
self.resolve_getter(id);
continue;
}
let Some(&ast_item) = self.hir_to_ast.get(&hir::ItemId::Function(id)) else {
debug_assert!(self.hir.function(id).is_yul);
continue;
};
let ast::ItemKind::Function(ast_func) = &ast_item.kind else { unreachable!() };
self.init(func.source, func.contract, Some(id));
let func = self.hir.function(id);
self.hir.functions[id].overrides =
self.lower_overrides(ast_func.header.override_.as_ref(), func.contract, true);
self.hir.functions[id].parameters = self.lower_variables(
*ast_func.header.parameters,
Some(hir::ItemId::Function(id)),
hir::VarKind::FunctionParam,
);
self.hir.functions[id].modifiers = {
let mut modifiers = SmallVec::<[_; 8]>::new();
for modifier in ast_func.header.modifiers.iter() {
let func = self.hir.function(id);
let expected = if func.kind.is_constructor() {
"base class or modifier"
} else {
"modifier"
};
let Ok(id) = self.resolve_path_as(&modifier.name, expected) else {
continue;
};
match id {
hir::ItemId::Contract(base)
if func.kind.is_constructor()
&& func.contract.is_some_and(|c| {
self.hir.contract(c).linearized_bases[1..].contains(&base)
}) => {}
hir::ItemId::Function(f) if self.hir.function(f).kind.is_modifier() => {
let modifier_contract = self.hir.function(f).contract;
let allowed = func.contract.is_some_and(|current| {
modifier_contract.is_some_and(|modifier_contract| {
self.hir
.contract(current)
.linearized_bases
.contains(&modifier_contract)
})
});
if !allowed {
self.dcx().emit_err(modifier.name.span(), "can only use modifiers defined in the current contract or in base contracts");
continue;
}
}
_ => {
self.resolver.report_expected(
expected,
self.hir.item(id).description(),
modifier.name.span(),
);
continue;
}
}
let args = self.lower_call_args(&modifier.arguments);
modifiers.push(hir::Modifier { span: modifier.span(), id, args });
}
self.arena.alloc_smallvec(modifiers)
};
self.hir.functions[id].returns = self.lower_variables(
ast_func.header.returns(),
Some(hir::ItemId::Function(id)),
hir::VarKind::FunctionReturn,
);
if let Some(body) = &ast_func.body {
self.hir.functions[id].body = Some(self.lower_block(body));
}
}
for id in self.hir.variable_ids().skip(normal_vars) {
self.resolve_var(id);
}
for id in self.hir.contract_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Contract(id)];
let ast::ItemKind::Contract(ast_contract) = &ast_item.kind else { unreachable!() };
let contract = self.hir.contract(id);
self.init(contract.source, Some(id), None);
self.hir.contracts[id].layout =
ast_contract.layout.as_ref().map(|layout| self.lower_expr(layout.slot));
}
}
#[instrument(level = "debug", skip_all)]
pub(super) fn resolve_using_directives(&mut self) {
for source_id in self.hir.source_ids() {
let Some(ast) = &self.sources[source_id].ast else { continue };
self.init(source_id, None, None);
let mut usings = SmallVec::<[_; 8]>::new();
for item in ast.items.iter() {
if let ast::ItemKind::Using(using) = &item.kind {
usings.push(self.lower_using_directive(item.span, using));
}
}
self.hir.sources[source_id].usings = self.arena.alloc_smallvec(usings);
}
for contract_id in self.hir.contract_ids() {
let ast_item = self.hir_to_ast[&hir::ItemId::Contract(contract_id)];
let ast::ItemKind::Contract(ast_contract) = &ast_item.kind else { unreachable!() };
let source = self.hir.contract(contract_id).source;
self.init(source, Some(contract_id), None);
let mut usings = SmallVec::<[_; 8]>::new();
for item in ast_contract.body.iter() {
if let ast::ItemKind::Using(using) = &item.kind {
usings.push(self.lower_using_directive(item.span, using));
}
}
self.hir.contracts[contract_id].usings = self.arena.alloc_smallvec(usings);
}
}
fn lower_using_directive(
&mut self,
span: Span,
using: &'gcx ast::UsingDirective<'gcx>,
) -> hir::UsingDirective<'gcx> {
let ty = using.ty.as_ref().map(|ty| self.lower_type(ty));
let entries = match &using.list {
ast::UsingList::Single(path) => {
let kind = match self.resolve_path_as::<hir::ContractId>(path, "library") {
Ok(id) => hir::UsingEntryKind::Library(id),
Err(guar) => hir::UsingEntryKind::Err(guar),
};
self.arena.alloc_as_slice(hir::UsingEntry {
span: path.span(),
name: None,
kind,
operator: None,
})
}
ast::UsingList::Multiple(paths) => {
self.arena.alloc_slice_fill_iter(paths.iter().map(|(path, operator)| {
let kind = self.lower_using_functions(path);
hir::UsingEntry {
span: path.span(),
name: Some(path.last().name),
kind,
operator: *operator,
}
}))
}
};
hir::UsingDirective {
span,
source: self.scopes.source.unwrap(),
contract: self.scopes.contract,
ty,
global: using.global,
entries,
}
}
fn lower_using_functions(&mut self, path: &ast::PathSlice) -> hir::UsingEntryKind<'gcx> {
let decls = match self.resolve_paths(path) {
Ok(decls) => decls,
Err(guar) => return hir::UsingEntryKind::Err(guar),
};
let mut functions = SmallVec::<[_; 4]>::new();
let mut saw_non_function = false;
let mut error = None;
for decl in decls {
match decl.res {
Res::Item(hir::ItemId::Function(id)) => functions.push(id),
Res::Err(guar) => error = Some(guar),
_ => saw_non_function = true,
}
}
if let Some(guar) = error {
return hir::UsingEntryKind::Err(guar);
}
if functions.len() != 1 || saw_non_function {
let guar = self.dcx().emit_err(path.span(), "expected function name");
return hir::UsingEntryKind::Err(guar);
}
hir::UsingEntryKind::Functions(self.arena.alloc_smallvec(functions))
}
#[instrument(level = "debug", skip_all)]
pub(super) fn resolve_base_args(&mut self) {
for c_id in self.hir.contract_ids() {
let contract = self.hir.contract(c_id);
self.init(contract.source, None, None);
self.scopes.enter();
let scope = self.scopes.current_scope();
scope.declare_unchecked(
sym::this,
Declaration { res: Res::Builtin(Builtin::This), span: Span::DUMMY },
);
scope.declare_unchecked(
sym::super_,
Declaration { res: Res::Builtin(Builtin::Super), span: Span::DUMMY },
);
self.resolve_base_args_inner(c_id);
self.scopes.exit();
}
}
fn resolve_base_args_inner(&mut self, c_id: hir::ContractId) {
let ast_contract = &self.hir_to_ast[&hir::ItemId::Contract(c_id)];
let ast::ItemKind::Contract(ast_contract) = &ast_contract.kind else { unreachable!() };
self.hir.contracts[c_id].bases_args = self.arena.alloc_from_iter(
std::iter::zip(&*ast_contract.bases, self.hir.contract(c_id).bases).map(
|(ast_base, &base_id)| hir::Modifier {
span: ast_base.span(),
id: base_id.into(),
args: self.lower_call_args(&ast_base.arguments),
},
),
);
let contract = self.hir.contract(c_id);
if contract.linearization_failed() {
return;
}
let len = contract.linearized_bases.len() - 1;
let base_args: &mut [Option<&'gcx hir::Modifier<'gcx>>] =
self.arena.alloc_from_iter(std::iter::repeat_n(None, len));
let mut resolve = |base: &'gcx hir::Modifier<'gcx>, is_ctor: bool| {
let Some(base_id) = base.id.as_contract() else { return };
let base_idx = contract
.linearized_bases
.iter()
.skip(1)
.position(|&l| l == base_id)
.expect("base contract not found");
if is_ctor && base.args.is_dummy() {
self.sess
.dcx
.emit_err(base.span, "modifier-style base constructor call without arguments");
}
let prev = &mut base_args[base_idx];
if let Some(prev) = prev {
if !prev.args.is_empty() {
self.sess
.dcx
.err("base constructor arguments given twice")
.span(base.span)
.span_help(prev.span, "previous declaration")
.emit();
} else if !prev.args.is_dummy() && !base.args.is_empty() {
self.sess
.dcx
.err("base constructor arguments given here")
.span(base.span)
.span_help(
prev.span,
"remove parentheses if you do not want to provide arguments here",
)
.emit();
}
}
*prev = Some(base);
};
for base in contract.bases_args {
resolve(base, false);
}
for base in contract.ctor.map_or(&[][..], |c| self.hir.function(c).modifiers) {
resolve(base, true);
}
self.hir.contracts[c_id].linearized_bases_args = base_args;
}
fn resolve_var(&mut self, id: hir::VariableId) {
let var = self.hir.variable(id);
let var_source = var.source;
let var_contract = var.contract;
let var_getter = var.getter;
let Some(&ast_item) = self.hir_to_ast.get(&hir::ItemId::Variable(id)) else {
assert!(!var.ty.is_dummy(), "{var:#?}");
return;
};
let ast::ItemKind::Variable(ast_var) = &ast_item.kind else { unreachable!() };
self.init(var_source, var_contract, None);
let init = ast_var.initializer.as_deref().map(|init| self.lower_expr(init));
let ty = self.lower_type(&ast_var.ty);
self.hir.variables[id].initializer = init;
self.hir.variables[id].ty = ty;
let overrides = self.lower_overrides(ast_var.override_.as_ref(), var_contract, false);
self.hir.variables[id].overrides = overrides;
if let Some(getter_id) = var_getter {
self.hir.functions[getter_id].overrides = overrides;
}
}
fn resolve_getter(&mut self, id: hir::FunctionId) {
let func = self.hir.function(id);
let Some(gettee) = func.gettee else { unreachable!() };
let ast_item = self.hir_to_ast[&hir::ItemId::Variable(gettee)];
let ast::ItemKind::Variable(ast_var) = &ast_item.kind else { unreachable!() };
let span = ast_var.span;
let mut ret_ty = &self.hir.variable(gettee).ty;
let mut ret_name = None;
let mut parameters = SmallVec::<[_; 8]>::new();
let new_param = |this: &mut Self, mut ty: hir::Type<'gcx>, mut name: Option<Ident>| {
ty.span = span;
if let Some(name) = &mut name {
name.span = span;
}
this.mk_var(
Some(hir::ItemId::Function(id)),
span,
ty,
name,
hir::VarKind::FunctionParam,
)
};
for i in 0usize.. {
let _ = i;
let index_name = || None;
match ret_ty.kind {
hir::TypeKind::Mapping(map) => {
let name = map.key_name.or_else(index_name);
let mut param = new_param(self, map.key.clone(), name);
if param.ty.kind.is_reference_type() {
param.data_location = Some(hir::DataLocation::Calldata);
}
parameters.push(self.hir.variables.push(param));
ret_ty = &map.value;
ret_name = map.value_name;
}
hir::TypeKind::Array(array) => {
let u256 = hir::Type {
kind: hir::TypeKind::Elementary(ast::ElementaryType::UInt(
ast::TypeSize::new_int_bits(256),
)),
span: ret_ty.span,
};
let param = new_param(self, u256, index_name());
parameters.push(self.hir.variables.push(param));
ret_ty = &array.element;
}
_ => break,
}
}
let ret_ty = ret_ty.clone();
if let Some(name) = &mut ret_name {
name.span = span;
}
let mut returns = SmallVec::<[_; 8]>::new();
let mut push_return =
|this: &mut Self, mut ty: hir::Type<'gcx>, mut name: Option<Ident>| {
ty.span = ret_ty.span;
if let Some(name) = &mut name {
name.span = span;
}
let mut ret = this.mk_var(
Some(hir::ItemId::Function(id)),
span,
ty,
name,
hir::VarKind::FunctionReturn,
);
if ret.ty.kind.is_reference_type() {
ret.data_location = Some(hir::DataLocation::Memory);
}
returns.push(this.hir.variables.push(ret))
};
let mut ret_struct = None;
if let hir::TypeKind::Custom(hir::ItemId::Struct(s_id)) = ret_ty.kind {
ret_struct = Some(s_id);
let s = self.hir.strukt(s_id);
for &field_id in s.fields.iter() {
let field = self.hir.variable(field_id);
if !matches!(field.ty.kind, hir::TypeKind::Mapping(_) | hir::TypeKind::Array(_)) {
push_return(self, field.ty.clone(), field.name);
}
}
} else {
push_return(self, ret_ty.clone(), ret_name);
}
self.hir.functions[id].parameters = self.arena.alloc_slice_copy(¶meters);
self.hir.functions[id].returns = self.arena.alloc_slice_copy(&returns);
self.hir.functions[id].body = {
let builder = self.hir_builder();
let res = Res::Item(hir::ItemId::Variable(gettee));
let mut expr = builder.expr(
self.next_id(),
hir::ExprKind::Ident(self.arena.alloc_as_slice(res)),
span,
);
for ¶m in ¶meters {
let res = Res::Item(hir::ItemId::Variable(param));
let ident = hir::ExprKind::Ident(self.arena.alloc_as_slice(res));
expr = builder.expr(
self.next_id(),
hir::ExprKind::Index(expr, Some(builder.expr(self.next_id(), ident, span))),
span,
);
}
let stmts: Option<&[hir::Stmt<'_>]> = match returns[..] {
[] => {
let msg = "getter must return at least one value";
let note = "the struct has all its members omitted, therefore the getter cannot return any values";
self.dcx().emit_err_span_note(span, msg, ret_ty.span, note);
Some(&[])
}
[_] if ret_struct.is_none() => Some(
self.arena
.alloc_as_slice(builder.stmt(hir::StmtKind::Return(Some(expr)), span)),
),
[..] => {
if let [ret] = returns[..] {
let ret = self.hir.variable(ret);
let expr = builder.expr(
self.next_id(),
hir::ExprKind::Member(expr, ret.name.unwrap()),
span,
);
let stmt = builder.stmt(hir::StmtKind::Return(Some(expr)), span);
Some(self.arena.alloc_as_slice(stmt))
} else {
let decl_name = Ident::new(sym::__tmp_struct, ast_var.span);
let mut decl_var = self.mk_var_stmt(id, span, ret_ty.clone(), decl_name);
decl_var.data_location = Some(hir::DataLocation::Storage);
decl_var.initializer = Some(expr);
let decl_id = self.hir.variables.push(decl_var);
let builder = self.hir_builder();
let decl_stmt = builder.stmt(hir::StmtKind::DeclSingle(decl_id), span);
let res =
self.arena.alloc_as_slice(Res::Item(hir::ItemId::Variable(decl_id)));
let tuple = builder.expr(
self.next_id(),
hir::ExprKind::Tuple(self.arena.alloc_slice_fill_iter(
returns.iter().map(|&ret| {
let decl_name_expr = builder.expr(
self.next_id(),
hir::ExprKind::Ident(res),
span,
);
let ret = self.hir.variable(ret);
Some(builder.expr(
self.next_id(),
hir::ExprKind::Member(decl_name_expr, ret.name.unwrap()),
span,
))
}),
)),
span,
);
let ret_stmt = builder.stmt(hir::StmtKind::Return(Some(tuple)), span);
Some(self.arena.alloc_array([decl_stmt, ret_stmt]))
}
}
};
stmts.map(|stmts| self.hir_builder().block(stmts, span))
}
}
fn mk_var(
&mut self,
parent: Option<hir::ItemId>,
span: Span,
ty: hir::Type<'gcx>,
name: Option<Ident>,
kind: hir::VarKind,
) -> hir::Variable<'gcx> {
hir::Variable {
contract: self.current_contract_id,
parent,
span,
..hir::Variable::new(self.current_source_id, hir::DocId::EMPTY, ty, name, kind)
}
}
fn mk_var_stmt(
&mut self,
function: hir::FunctionId,
span: Span,
ty: hir::Type<'gcx>,
name: Ident,
) -> hir::Variable<'gcx> {
self.mk_var(
Some(hir::ItemId::Function(function)),
span,
ty,
Some(name),
hir::VarKind::Statement,
)
}
fn in_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.scopes.enter();
let t = f(self);
self.scopes.exit();
t
}
fn in_yul_scope<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.scopes.enter();
self.yul_scopes.push(self.scopes.scopes.len() - 1);
let t = f(self);
self.yul_scopes.pop();
self.scopes.exit();
t
}
fn in_scope_if<T>(&mut self, cond: bool, f: impl FnOnce(&mut Self) -> T) -> T {
if cond { self.in_scope(f) } else { f(self) }
}
fn resolve_paths<'a>(
&'a self,
path: &ast::PathSlice,
) -> Result<&'a [Declaration], ErrorGuaranteed> {
self.resolver.resolve_paths(path, &self.scopes).map_err(self.resolver.emit_resolver_error())
}
fn resolve_path(&self, path: &ast::PathSlice) -> Result<&'gcx [Res], ErrorGuaranteed> {
self.resolve_paths(path)
.map(|decls| &*self.arena.alloc_slice_fill_iter(decls.iter().map(|decl| decl.res)))
}
fn resolve_path_as<T: TryFrom<Res>>(
&self,
path: &ast::PathSlice,
description: &str,
) -> Result<T, ErrorGuaranteed> {
self.resolver.resolve_path_as(path, &self.scopes, description)
}
fn lower_block(&mut self, block: &ast::Block<'_>) -> hir::Block<'gcx> {
self.in_scope_if(!block.is_empty(), |this| this.lower_stmts(block.stmts, block.span))
}
fn lower_stmts(&mut self, stmts: &[ast::Stmt<'_>], span: Span) -> hir::Block<'gcx> {
let stmts =
self.arena.alloc_slice_fill_iter(stmts.iter().map(|stmt| self.lower_stmt_full(stmt)));
hir::Block { span, stmts }
}
fn lower_stmt(&mut self, stmt: &ast::Stmt<'_>) -> &'gcx hir::Stmt<'gcx> {
self.arena.alloc_with(|| self.lower_stmt_full(stmt))
}
#[instrument(name = "lower_stmt", level = "trace", skip_all)]
fn lower_stmt_full(&mut self, stmt: &ast::Stmt<'_>) -> hir::Stmt<'gcx> {
let kind = match &stmt.kind {
ast::StmtKind::DeclSingle(var) => {
match self.lower_variable(
var,
self.function_id.map(hir::ItemId::Function),
hir::VarKind::Statement,
) {
(id, Ok(())) => hir::StmtKind::DeclSingle(id),
(_, Err(guar)) => hir::StmtKind::Err(guar),
}
}
ast::StmtKind::DeclMulti(vars, expr) => hir::StmtKind::DeclMulti(
self.arena.alloc_slice_fill_iter(vars.iter().map(|var| {
var.as_ref().unspan().map(|var| {
self.lower_variable(
var,
self.function_id.map(hir::ItemId::Function),
hir::VarKind::Statement,
)
.0
})
})),
self.lower_expr(expr),
),
ast::StmtKind::Assembly(assembly) => self.lower_yul_assembly(assembly),
ast::StmtKind::Block(stmts) => hir::StmtKind::Block(self.lower_block(stmts)),
ast::StmtKind::UncheckedBlock(stmts) => {
hir::StmtKind::UncheckedBlock(self.lower_block(stmts))
}
ast::StmtKind::Break => hir::StmtKind::Break,
ast::StmtKind::Continue => hir::StmtKind::Continue,
ast::StmtKind::Return(expr) => {
hir::StmtKind::Return(self.lower_expr_opt(expr.as_deref()))
}
ast::StmtKind::While(_, _)
| ast::StmtKind::DoWhile(_, _)
| ast::StmtKind::For { .. } => self.lower_loop_stmt(stmt),
ast::StmtKind::Emit(path, args) => match self.resolve_path(path) {
Ok(res) => {
hir::StmtKind::Emit(self.make_call_expr_for_emit(path, res, args, stmt.span))
}
Err(guar) => hir::StmtKind::Err(guar),
},
ast::StmtKind::Revert(path, args) => match self.resolve_path(path) {
Ok(res) => {
hir::StmtKind::Revert(self.make_call_expr_for_emit(path, res, args, stmt.span))
}
Err(guar) => hir::StmtKind::Err(guar),
},
ast::StmtKind::Expr(expr) => hir::StmtKind::Expr(self.lower_expr(expr)),
ast::StmtKind::If(cond, then, else_) => hir::StmtKind::If(
self.lower_expr(cond),
self.lower_stmt(then),
else_.as_deref().map(|stmt| self.lower_stmt(stmt)),
),
ast::StmtKind::Try(ast::StmtTry { expr, clauses }) => {
hir::StmtKind::Try(self.arena.alloc(hir::StmtTry {
expr: self.lower_expr_full(expr),
clauses: self.arena.alloc_slice_fill_iter(
clauses.iter().map(|catch| self.lower_try_catch_clause(catch)),
),
}))
}
ast::StmtKind::Placeholder => hir::StmtKind::Placeholder,
};
hir::Stmt { span: stmt.span, kind }
}
fn lower_yul_assembly(&mut self, assembly: &ast::StmtAssembly<'_>) -> hir::StmtKind<'gcx> {
let mut memory_safe = false;
for flag in assembly.flags.iter() {
let span = flag.span;
match flag.value {
sym::memory_dash_safe => {
if memory_safe {
self.dcx()
.emit_err(span, "inline assembly marked memory-safe multiple times");
}
memory_safe = true;
}
_ => self
.dcx()
.warn("unknown inline assembly flag")
.code(error_code!(4430))
.span(span)
.emit(),
}
}
hir::StmtKind::AssemblyBlock(self.lower_yul_block(&assembly.block))
}
fn lower_yul_block(&mut self, block: &ast::yul::Block<'_>) -> hir::Block<'gcx> {
self.in_yul_scope(|this| this.lower_yul_stmts(block.stmts, block.span))
}
fn lower_yul_stmts(&mut self, stmts: &[ast::yul::Stmt<'_>], span: Span) -> hir::Block<'gcx> {
let functions = self.collect_yul_function_decls(stmts);
for &(function, id) in &functions {
self.declare_yul_function(function, id);
}
for (function, id) in functions {
self.lower_yul_function_body(function, id);
}
let stmts =
self.arena.alloc_from_iter(stmts.iter().filter_map(|stmt| self.lower_yul_stmt(stmt)));
hir::Block { span, stmts }
}
fn collect_yul_function_decls<'stmt, 'ast>(
&mut self,
stmts: &'stmt [ast::yul::Stmt<'ast>],
) -> SmallVec<[(&'stmt ast::yul::Function<'ast>, hir::FunctionId); 4]> {
let mut functions = SmallVec::new();
for stmt in stmts {
let ast::yul::StmtKind::FunctionDef(function) = &stmt.kind else { continue };
let id = self.lcx.yul_functions[&super::yul_function_key(function)];
functions.push((function, id));
}
functions
}
fn declare_yul_function(&mut self, function: &ast::yul::Function<'_>, id: hir::FunctionId) {
let res = Res::Item(hir::ItemId::Function(id));
let _ = self.scopes.current_scope().declare_res(
self.lcx.sess,
&self.lcx.hir,
function.name,
res,
);
}
fn lower_yul_function_body(&mut self, function: &ast::yul::Function<'_>, id: hir::FunctionId) {
let previous_function = self.function_id.replace(id);
self.scopes.enter();
let function_scope = self.scopes.scopes.len() - 1;
self.yul_scopes.push(function_scope);
let previous_yul_function_scope = self.yul_function_scope.replace(function_scope);
self.hir.functions[id].parameters =
self.lower_yul_function_variables(id, function.parameters, hir::VarKind::FunctionParam);
self.hir.functions[id].returns =
self.lower_yul_function_variables(id, function.returns, hir::VarKind::FunctionReturn);
let block = self.lower_yul_block(&function.body);
let unchecked = self.hir_builder().stmt(hir::StmtKind::AssemblyBlock(block), block.span);
let body = self.hir_builder().block(self.arena.alloc_as_slice(unchecked), block.span);
self.yul_function_scope = previous_yul_function_scope;
self.yul_scopes.pop();
self.scopes.exit();
self.hir.functions[id].body = Some(body);
self.function_id = previous_function;
}
fn lower_yul_function_variables(
&mut self,
function: hir::FunctionId,
idents: &[Ident],
kind: hir::VarKind,
) -> &'gcx [hir::VariableId] {
self.arena.alloc_slice_fill_iter(
idents.iter().map(|&ident| self.lower_yul_function_variable(function, ident, kind)),
)
}
fn lower_yul_function_variable(
&mut self,
function: hir::FunctionId,
name: Ident,
kind: hir::VarKind,
) -> hir::VariableId {
let var = self.mk_yul_var(
Some(function),
name.span,
self.yul_uint_type(name.span),
Some(name),
kind,
);
let id = self.hir.variables.push(var);
let res = Res::Item(hir::ItemId::Variable(id));
let _ = self.scopes.current_scope().declare_res(self.lcx.sess, &self.lcx.hir, name, res);
id
}
fn lower_yul_stmt(&mut self, stmt: &ast::yul::Stmt<'_>) -> Option<hir::Stmt<'gcx>> {
let kind = match &stmt.kind {
ast::yul::StmtKind::Block(block) => hir::StmtKind::Block(self.lower_yul_block(block)),
ast::yul::StmtKind::AssignSingle(path, expr) => {
let lhs = self.lower_yul_path_expr(path);
let rhs = self.lower_yul_expr(expr);
hir::StmtKind::Expr(self.hir_builder().expr(
self.next_id(),
hir::ExprKind::Assign(lhs, None, rhs),
stmt.span,
))
}
ast::yul::StmtKind::AssignMulti(paths, expr) => {
let components = self.arena.alloc_slice_fill_iter(
paths.iter().map(|path| Some(self.lower_yul_path_expr(path))),
);
let lhs = self.hir_builder().expr(
self.next_id(),
hir::ExprKind::Tuple(components),
Span::join_first_last(paths.iter().map(|path| path.span())),
);
let rhs = self.lower_yul_expr(expr);
hir::StmtKind::Expr(self.hir_builder().expr(
self.next_id(),
hir::ExprKind::Assign(lhs, None, rhs),
stmt.span,
))
}
ast::yul::StmtKind::Expr(expr) => hir::StmtKind::Expr(self.lower_yul_expr(expr)),
ast::yul::StmtKind::If(cond, block) => {
let cond = self.lower_yul_condition(cond);
let block = self.lower_yul_block(block);
let body = self.hir_builder().stmt_alloc(hir::StmtKind::Block(block), block.span);
hir::StmtKind::If(cond, body, None)
}
ast::yul::StmtKind::For(for_) => self.lower_yul_for_stmt(for_),
ast::yul::StmtKind::Switch(switch) => self.lower_yul_switch_stmt(switch),
ast::yul::StmtKind::Leave => hir::StmtKind::Return(None),
ast::yul::StmtKind::Break => hir::StmtKind::Break,
ast::yul::StmtKind::Continue => hir::StmtKind::Continue,
ast::yul::StmtKind::FunctionDef(_) => return None,
ast::yul::StmtKind::VarDecl(idents, expr) => {
self.lower_yul_var_decl(idents, expr.as_ref(), stmt.span)
}
};
Some(hir::Stmt { span: stmt.span, kind })
}
fn lower_yul_var_decl(
&mut self,
idents: &[Ident],
expr: Option<&ast::yul::Expr<'_>>,
span: Span,
) -> hir::StmtKind<'gcx> {
let vars = idents
.iter()
.map(|&ident| self.lower_yul_variable(ident))
.collect::<SmallVec<[_; 4]>>();
if let Some(guar) = vars.iter().find_map(|(_, result)| result.err()) {
return hir::StmtKind::Err(guar);
}
match (vars.as_slice(), expr) {
([(var, _)], None) => hir::StmtKind::DeclSingle(*var),
([(var, _)], Some(expr)) => {
self.hir.variables[*var].initializer = Some(self.lower_yul_expr(expr));
hir::StmtKind::DeclSingle(*var)
}
(vars, None) => {
let stmts = self.arena.alloc_slice_fill_iter(
vars.iter()
.map(|&(var, _)| hir::Stmt { span, kind: hir::StmtKind::DeclSingle(var) }),
);
hir::StmtKind::Block(hir::Block { span, stmts })
}
(vars, Some(expr)) => hir::StmtKind::DeclMulti(
self.arena.alloc_slice_fill_iter(vars.iter().map(|&(var, _)| Some(var))),
self.lower_yul_expr(expr),
),
}
}
fn lower_yul_variable(
&mut self,
name: Ident,
) -> (hir::VariableId, Result<(), ErrorGuaranteed>) {
let var = self.mk_yul_var(
self.function_id,
name.span,
self.yul_uint_type(name.span),
Some(name),
hir::VarKind::Statement,
);
let id = self.hir.variables.push(var);
let res = Res::Item(hir::ItemId::Variable(id));
let result =
self.scopes.current_scope().declare_res(self.lcx.sess, &self.lcx.hir, name, res);
(id, result)
}
fn lower_yul_for_stmt(&mut self, for_: &ast::yul::StmtFor<'_>) -> hir::StmtKind<'gcx> {
self.in_yul_scope(|this| {
let init = this.lower_yul_stmts(for_.init.stmts, for_.init.span);
let cond = this.lower_yul_condition(&for_.cond);
let step =
this.in_yul_scope(|this| this.lower_yul_stmts(for_.step.stmts, for_.step.span));
let body =
this.in_yul_scope(|this| this.lower_yul_stmts(for_.body.stmts, for_.body.span));
let builder = this.hir_builder();
let step_stmt = builder.stmt(hir::StmtKind::Block(step), step.span);
let body = builder.stmt(
hir::StmtKind::Block(builder.block(
this.arena.alloc_array([
builder.stmt(hir::StmtKind::Block(body), body.span),
step_stmt,
]),
for_.body.span,
)),
for_.body.span,
);
let break_stmt = builder.break_stmt(for_.cond.span);
let loop_body = builder.stmt(
hir::StmtKind::If(cond, this.arena.alloc(body), Some(break_stmt)),
for_.body.span,
);
let loop_stmt = builder.stmt(
hir::StmtKind::Loop(
builder.block(this.arena.alloc_as_slice(loop_body), for_.body.span),
hir::LoopSource::For,
),
for_.body.span,
);
hir::StmtKind::Block(builder.block(
this.arena.alloc_array([
builder.stmt(hir::StmtKind::Block(init), for_.init.span),
loop_stmt,
]),
for_.init.span.with_hi(for_.body.span.hi()),
))
})
}
fn lower_yul_switch_stmt(&mut self, switch: &ast::yul::StmtSwitch<'_>) -> hir::StmtKind<'gcx> {
hir::StmtKind::Switch(self.arena.alloc(hir::StmtSwitch {
selector: self.lower_yul_expr(&switch.selector),
cases: self.arena.alloc_slice_fill_iter(switch.cases.iter().map(|case| {
hir::StmtSwitchCase {
span: case.span,
constant: case.constant.as_ref().map(|lit| self.lower_lit(lit)),
body: self.lower_yul_block(&case.body),
}
})),
}))
}
fn lower_yul_condition(&mut self, expr: &ast::yul::Expr<'_>) -> &'gcx hir::Expr<'gcx> {
match &expr.kind {
ast::yul::ExprKind::Lit(lit) if matches!(lit.kind, ast::LitKind::Bool(_)) => {
return self.lower_yul_expr(expr);
}
_ => {}
}
let expr = self.lower_yul_expr(expr);
let zero = self.yul_number_lit(U256::ZERO, expr.span);
self.hir_builder().expr(
self.next_id(),
hir::ExprKind::Binary(
expr,
hir::BinOp { span: expr.span, kind: hir::BinOpKind::Ne },
zero,
),
expr.span,
)
}
fn lower_yul_expr(&mut self, expr: &ast::yul::Expr<'_>) -> &'gcx hir::Expr<'gcx> {
self.arena.alloc_with(|| self.lower_yul_expr_full(expr))
}
fn lower_yul_expr_full(&mut self, expr: &ast::yul::Expr<'_>) -> hir::Expr<'gcx> {
let kind = match &expr.kind {
ast::yul::ExprKind::Path(path) => return self.lower_yul_path_expr_full(path),
ast::yul::ExprKind::Call(call) => self.lower_yul_call(call, expr.span),
ast::yul::ExprKind::Lit(lit) => hir::ExprKind::Lit(self.lower_lit(lit)),
};
self.hir_builder().expr_owned(self.next_id(), kind, expr.span)
}
fn lower_yul_call(&mut self, call: &ast::yul::ExprCall<'_>, span: Span) -> hir::ExprKind<'gcx> {
match self.resolve_yul_call_name(call.name) {
Ok(res) => {
let callee = self.hir_builder().expr(
self.next_id(),
hir::ExprKind::Ident(res),
call.name.span,
);
hir::ExprKind::Call(callee, self.lower_yul_call_args(call.arguments, span), None)
}
Err(guar) => hir::ExprKind::Err(guar),
}
}
fn resolve_yul_call_name(&mut self, name: Ident) -> Result<&'gcx [Res], ErrorGuaranteed> {
for scope in self.scopes.scopes.iter().rev() {
let Some(decls) = scope.resolve(name) else { continue };
if let Some(guar) = decls.iter().find_map(|decl| match decl.res {
Res::Err(guar) => Some(guar),
_ => None,
}) {
return Err(guar);
}
let functions = decls
.iter()
.filter_map(|decl| match decl.res {
Res::Item(hir::ItemId::Function(id)) if self.hir.function(id).is_yul => {
Some(Res::Item(hir::ItemId::Function(id)))
}
_ => None,
})
.collect::<SmallVec<[_; 4]>>();
if functions.is_empty() {
if decls.iter().all(|decl| matches!(decl.res, Res::Item(hir::ItemId::Function(_))))
{
return Err(self.resolver.emit_resolver_error()(ResolverError::new(
name,
ResolverErrorKind::Unresolved,
)));
}
return Err(self.resolver.report_expected(
"function",
decls[0].res.description(),
name.span,
));
}
let functions = self.arena.alloc_smallvec(functions);
return Ok(&*functions);
}
if let Some(builtin) = Builtin::from_yul_name(name.name) {
return Ok(self.arena.alloc_as_slice(Res::Builtin(builtin)));
}
if name.name.as_str().starts_with("verbatim_") {
return Err(self.dcx().emit_err(name.span, "unsupported verbatim builtin"));
}
Err(self.resolver.emit_resolver_error()(ResolverError::new(
name,
ResolverErrorKind::Unresolved,
)))
}
fn lower_yul_call_args(
&mut self,
arguments: &[ast::yul::Expr<'_>],
span: Span,
) -> hir::CallArgs<'gcx> {
hir::CallArgs {
span,
kind: hir::CallArgsKind::Unnamed(
self.arena.alloc_slice_fill_iter(
arguments.iter().map(|arg| self.lower_yul_expr_full(arg)),
),
),
}
}
fn lower_yul_path_expr(&mut self, path: &ast::PathSlice) -> &'gcx hir::Expr<'gcx> {
self.arena.alloc_with(|| self.lower_yul_path_expr_full(path))
}
fn lower_yul_path_expr_full(&mut self, path: &ast::PathSlice) -> hir::Expr<'gcx> {
let segments = path.segments();
let first = segments.first().copied().unwrap();
let kind = match self.resolve_yul_paths(first) {
Ok(decls) => hir::ExprKind::Ident(
self.arena.alloc_slice_fill_iter(decls.iter().map(|decl| decl.res)),
),
Err(guar) => hir::ExprKind::Err(guar),
};
let Some((&last, rest)) = segments[1..].split_last() else {
return self.hir_builder().expr_owned(self.next_id(), kind, first.span);
};
let mut expr = self.hir_builder().expr(self.next_id(), kind, first.span);
for &segment in rest {
expr = self.hir_builder().expr(
self.next_id(),
hir::ExprKind::YulMember(expr, segment),
segment.span,
);
}
self.hir_builder().expr_owned(
self.next_id(),
hir::ExprKind::YulMember(expr, last),
last.span,
)
}
fn resolve_yul_paths(&self, name: Ident) -> Result<&[Declaration], ErrorGuaranteed> {
let function_scope = self.yul_function_scope;
for (index, scope) in self.scopes.scopes.iter().enumerate().rev() {
let Some(decls) = scope.resolve(name) else { continue };
if function_scope.is_some_and(|function_scope| index < function_scope)
&& decls.iter().all(|decl| decl.res.as_variable().is_some())
{
continue;
}
return Ok(decls);
}
self.resolver
.resolve_name_non_local(name, &self.scopes)
.map_err(self.resolver.emit_resolver_error())
}
fn yul_number_lit(&mut self, value: U256, span: Span) -> &'gcx hir::Expr<'gcx> {
let lit = self.arena.alloc(ast::Lit {
span,
symbol: {
if let Ok(small) = u128::try_from(value) {
sym::integer(small)
} else {
Symbol::intern(&value.to_string())
}
},
kind: ast::LitKind::Number(value),
});
self.hir_builder().expr(self.next_id(), hir::ExprKind::Lit(lit), span)
}
fn yul_uint_type(&self, span: Span) -> hir::Type<'gcx> {
hir::Type {
span,
kind: hir::TypeKind::Elementary(ast::ElementaryType::UInt(
ast::TypeSize::new_int_bits(256),
)),
}
}
fn mk_yul_var(
&mut self,
function: Option<hir::FunctionId>,
span: Span,
ty: hir::Type<'gcx>,
name: Option<Ident>,
kind: hir::VarKind,
) -> hir::Variable<'gcx> {
let mut var = self.mk_var(function.map(Into::into), span, ty, name, kind);
var.source = self.scopes.source.unwrap_or(self.current_source_id);
var.contract = self.scopes.contract;
var
}
fn lower_try_catch_clause(
&mut self,
&ast::TryCatchClause { span, name, ref args, ref block }: &ast::TryCatchClause<'_>,
) -> hir::TryCatchClause<'gcx> {
self.in_scope(|this| hir::TryCatchClause {
span,
name,
args: this.lower_variables(
args.vars,
this.function_id.map(hir::ItemId::Function),
hir::VarKind::TryCatch,
),
block: this.lower_block(block),
})
}
fn make_call_expr_for_emit(
&mut self,
path: &ast::PathSlice,
res: &'gcx [Res],
args: &ast::CallArgs<'_>,
span: Span,
) -> &'gcx hir::Expr<'gcx> {
self.arena.alloc(hir::Expr {
kind: hir::ExprKind::Call(
self.arena.alloc(hir::Expr {
id: self.next_id(),
kind: hir::ExprKind::Ident(res),
span: path.last().span,
}),
self.lower_call_args(args),
None,
),
id: self.next_id(),
span,
})
}
fn lower_variables(
&mut self,
vars: &[ast::VariableDefinition<'_>],
parent: Option<hir::ItemId>,
kind: hir::VarKind,
) -> &'gcx [hir::VariableId] {
self.arena
.alloc_slice_fill_iter(vars.iter().map(|var| self.lower_variable(var, parent, kind).0))
}
fn lower_variable(
&mut self,
var: &ast::VariableDefinition<'_>,
parent: Option<hir::ItemId>,
kind: hir::VarKind,
) -> (hir::VariableId, Result<(), ErrorGuaranteed>) {
let id = super::lower::lower_variable_partial(
&mut self.lcx.hir,
var,
self.scopes.source.unwrap(),
self.scopes.contract,
parent,
kind,
hir::DocId::EMPTY,
);
self.hir.variables[id].ty = self.lower_type(&var.ty);
self.hir.variables[id].initializer = self.lower_expr_opt(var.initializer.as_deref());
let mut guar = Ok(());
if let Some(name) = var.name {
let res = Res::Item(hir::ItemId::Variable(id));
guar = self.scopes.current_scope().declare_res(self.lcx.sess, &self.lcx.hir, name, res);
}
(id, guar)
}
fn lower_loop_stmt(&mut self, stmt: &ast::Stmt<'_>) -> hir::StmtKind<'gcx> {
let span = stmt.span;
match &stmt.kind {
ast::StmtKind::While(cond, stmt) => self.in_scope(|this| {
let cond = this.lower_expr(cond);
let stmt = this.lower_stmt(stmt);
let builder = this.hir_builder();
let break_stmt = builder.break_stmt(span);
let body = this.arena.alloc_as_slice(
builder.stmt(hir::StmtKind::If(cond, stmt, Some(break_stmt)), span),
);
hir::StmtKind::Loop(builder.block(body, span), hir::LoopSource::While)
}),
ast::StmtKind::DoWhile(stmt, cond) => self.in_scope(|this| {
let stmt = this.in_scope(|this| this.lower_stmt_full(stmt));
let cond = this.lower_expr(cond);
let builder = this.hir_builder();
let cont_stmt = builder.continue_stmt(span);
let break_stmt = builder.break_stmt(span);
let check =
builder.stmt(hir::StmtKind::If(cond, cont_stmt, Some(break_stmt)), span);
let body = this.arena.alloc_array([stmt, check]);
hir::StmtKind::Loop(builder.block(body, span), hir::LoopSource::DoWhile)
}),
ast::StmtKind::For { init, cond, next, body } => {
self.in_scope_if(init.is_some(), |this| {
let init = init.as_deref().map(|stmt| this.lower_stmt_full(stmt));
let cond = this.lower_expr_opt(cond.as_deref());
let mut body =
this.in_scope_if(next.is_some(), |this| this.lower_stmt_full(body));
let next = this.lower_expr_opt(next.as_deref());
let builder = this.hir_builder();
if let Some(next) = next {
let next = builder.stmt(hir::StmtKind::Expr(next), next.span);
let body_span = body.span;
body = builder.stmt(
hir::StmtKind::Block(
builder.block(this.arena.alloc_array([body, next]), span),
),
body_span,
);
}
if let Some(cond) = cond {
let break_stmt = builder.break_stmt(span);
let body_span = body.span;
body = builder.stmt(
hir::StmtKind::If(cond, this.arena.alloc(body), Some(break_stmt)),
body_span,
);
}
let mut kind = hir::StmtKind::Loop(
builder.block(this.arena.alloc_as_slice(body), span),
hir::LoopSource::For,
);
if let Some(init) = init {
let s = builder.stmt(kind, span);
kind = hir::StmtKind::Block(
builder.block(this.arena.alloc_array([init, s]), span),
);
}
kind
})
}
_ => unreachable!(),
}
}
fn lower_expr(&mut self, expr: &ast::Expr<'_>) -> &'gcx hir::Expr<'gcx> {
self.arena.alloc_with(|| self.lower_expr_full(expr))
}
fn lower_expr_opt(&mut self, expr: Option<&ast::Expr<'_>>) -> Option<&'gcx hir::Expr<'gcx>> {
expr.map(|expr| self.lower_expr(expr))
}
fn lower_exprs<'b, I, T>(&mut self, exprs: I) -> &'gcx [hir::Expr<'gcx>]
where
I: IntoIterator<Item = T>,
I::IntoIter: ExactSizeIterator,
T: AsRef<ast::Expr<'b>>,
{
self.arena
.alloc_slice_fill_iter(exprs.into_iter().map(|e| self.lower_expr_full(e.as_ref())))
}
#[instrument(name = "lower_expr", level = "trace", skip_all)]
fn lower_expr_full(&mut self, expr: &ast::Expr<'_>) -> hir::Expr<'gcx> {
let kind = match &expr.kind {
ast::ExprKind::Array(exprs) => hir::ExprKind::Array(self.lower_exprs(&**exprs)),
ast::ExprKind::Assign(lhs, op, rhs) => {
hir::ExprKind::Assign(self.lower_expr(lhs), *op, self.lower_expr(rhs))
}
ast::ExprKind::Binary(lhs, op, rhs) => {
hir::ExprKind::Binary(self.lower_expr(lhs), *op, self.lower_expr(rhs))
}
ast::ExprKind::Call(callee, args) => {
let (callee, options) = self.lower_call_callee(callee);
hir::ExprKind::Call(callee, self.lower_call_args(args), options)
}
ast::ExprKind::CallOptions(callee, options) => {
let callee = self.lower_expr(callee);
let _options = self.lower_named_args(options);
let options_span = callee.span.shrink_to_hi().with_hi(expr.span.hi());
hir::ExprKind::Err(self.sess.dcx.emit_err_span_note(
options_span,
"call options must be part of a call expression",
expr.span,
"this expression is not a function call expression",
))
}
ast::ExprKind::Delete(expr) => hir::ExprKind::Delete(self.lower_expr(expr)),
ast::ExprKind::Ident(name) => {
match self.resolve_paths(ast::PathSlice::from_ref(name)) {
Ok(decls) => hir::ExprKind::Ident(
self.arena.alloc_slice_fill_iter(decls.iter().map(|decl| decl.res)),
),
Err(guar) => hir::ExprKind::Err(guar),
}
}
ast::ExprKind::Index(expr, index) => match index {
ast::IndexKind::Index(index) => hir::ExprKind::Index(
self.lower_expr(expr),
self.lower_expr_opt(index.as_deref()),
),
ast::IndexKind::Range(start, end) => hir::ExprKind::Slice(
self.lower_expr(expr),
self.lower_expr_opt(start.as_deref()),
self.lower_expr_opt(end.as_deref()),
),
},
ast::ExprKind::Lit(lit, _) => hir::ExprKind::Lit(self.lower_lit(lit)),
ast::ExprKind::Member(expr, member) => {
hir::ExprKind::Member(self.lower_expr(expr), *member)
}
ast::ExprKind::New(ty) => hir::ExprKind::New(self.lower_type(ty)),
ast::ExprKind::Payable(args) => 'b: {
if let ast::CallArgsKind::Unnamed(args) = &args.kind
&& let [arg] = &args[..]
{
break 'b hir::ExprKind::Payable(self.lower_expr(arg));
}
let msg = "expected exactly one unnamed argument";
let guar = self.sess.dcx.emit_err(expr.span, msg);
hir::ExprKind::Err(guar)
}
ast::ExprKind::Ternary(cond, then, r#else) => hir::ExprKind::Ternary(
self.lower_expr(cond),
self.lower_expr(then),
self.lower_expr(r#else),
),
ast::ExprKind::Tuple(exprs) => hir::ExprKind::Tuple(self.arena.alloc_slice_fill_iter(
exprs.iter().map(|expr| self.lower_expr_opt(expr.as_deref().unspan())),
)),
ast::ExprKind::TypeCall(ty) => hir::ExprKind::TypeCall(self.lower_type(ty)),
ast::ExprKind::Type(ty) => hir::ExprKind::Type(self.lower_type(ty)),
ast::ExprKind::Unary(op, expr) => hir::ExprKind::Unary(*op, self.lower_expr(expr)),
ast::ExprKind::Err(guar) => hir::ExprKind::Err(*guar),
};
self.hir_builder().expr_owned(self.next_id(), kind, expr.span)
}
fn lower_lit(&mut self, lit: &ast::Lit<'_>) -> &'gcx ast::Lit<'gcx> {
self.arena.alloc_with(|| lit.copy_without_data())
}
fn lower_call_callee(
&mut self,
callee: &ast::Expr<'_>,
) -> (&'gcx hir::Expr<'gcx>, Option<&'gcx hir::CallOptions<'gcx>>) {
let mut inner = callee.peel_parens();
let mut options = None;
let mut has_nested_options = false;
while let ast::ExprKind::CallOptions(next, args) = &inner.kind {
if options.is_some() {
has_nested_options = true;
}
options =
Some(hir::CallOptions { span: inner.span, args: self.lower_named_args(args) });
inner = next.peel_parens();
}
let Some(options) = options else {
return (self.lower_expr(inner), None);
};
if has_nested_options {
self.sess
.dcx
.err("function call options have already been set")
.span(callee.span)
.help("combine them into a single `{...}` option")
.emit();
}
(self.lower_expr(inner), Some(self.arena.alloc(options)))
}
fn lower_named_args(&mut self, options: &[ast::NamedArg<'_>]) -> &'gcx [hir::NamedArg<'gcx>] {
self.arena.alloc_slice_fill_iter(
options.iter().map(|arg| hir::NamedArg {
name: arg.name,
value: self.lower_expr_full(arg.value),
}),
)
}
fn lower_call_args(&mut self, args: &ast::CallArgs<'_>) -> hir::CallArgs<'gcx> {
let kind = match &args.kind {
ast::CallArgsKind::Unnamed(args) => {
hir::CallArgsKind::Unnamed(self.lower_exprs(&**args))
}
ast::CallArgsKind::Named(args) => hir::CallArgsKind::Named(self.lower_named_args(args)),
};
hir::CallArgs { kind, span: args.span }
}
#[instrument(name = "lower_type", level = "trace", skip_all)]
fn lower_type(&mut self, ty: &ast::Type<'_>) -> hir::Type<'gcx> {
let kind = match &ty.kind {
ast::TypeKind::Elementary(ty) => hir::TypeKind::Elementary(match *ty {
ast::ElementaryType::Int(size) if size == ast::TypeSize::ZERO => {
ast::ElementaryType::Int(ast::TypeSize::new_int_bits(256))
}
ast::ElementaryType::UInt(size) if size == ast::TypeSize::ZERO => {
ast::ElementaryType::UInt(ast::TypeSize::new_int_bits(256))
}
ty => ty,
}),
ast::TypeKind::Array(array) => hir::TypeKind::Array(self.arena.alloc(hir::TypeArray {
element: self.lower_type(&array.element),
size: self.lower_expr_opt(array.size.as_deref()),
})),
ast::TypeKind::Function(f) => hir::TypeKind::Function(
self.arena.alloc(hir::TypeFunction {
parameters: self.lower_variables(
*f.parameters,
self.function_id.map(hir::ItemId::Function),
hir::VarKind::FunctionTyParam,
),
visibility: f.visibility.map(|v| *v).unwrap_or(ast::Visibility::Public),
state_mutability: f
.state_mutability
.map(|s| s.data)
.unwrap_or(ast::StateMutability::NonPayable),
returns: self.lower_variables(
f.returns(),
self.function_id.map(hir::ItemId::Function),
hir::VarKind::FunctionTyReturn,
),
}),
),
ast::TypeKind::Mapping(mapping) => {
hir::TypeKind::Mapping(self.arena.alloc(hir::TypeMapping {
key: self.lower_type(&mapping.key),
key_name: mapping.key_name,
value: self.lower_type(&mapping.value),
value_name: mapping.value_name,
}))
}
ast::TypeKind::Custom(path) => match self.resolve_path_as(path, "item") {
Ok(id) => hir::TypeKind::Custom(id),
Err(guar) => hir::TypeKind::Err(guar),
},
};
hir::Type { kind, span: ty.span }
}
#[inline]
fn next_id<I: Idx>(&self) -> I {
self.next_id.next()
}
fn hir_builder(&self) -> hir::HirBuilder<'gcx, '_> {
hir::Hir::builder(self.arena, &self.next_id)
}
fn lower_overrides(
&mut self,
override_: Option<&ast::Override<'_>>,
contract: Option<hir::ContractId>,
is_function: bool,
) -> &'gcx [hir::ContractId] {
let Some(ov) = override_ else {
return &[];
};
let mut overrides = SmallVec::<[hir::ContractId; 8]>::new();
for path in ov.paths.iter() {
let Ok(id) = self.resolver.resolve_path_as(path, &self.scopes, "contract") else {
continue;
};
let Some(c) = contract else {
if is_function {
self.dcx()
.err("free functions cannot override")
.code(error_code!(1750))
.span(ov.span)
.emit();
}
continue;
};
if !self.hir.contract(c).linearized_bases[1..].contains(&id) {
self.dcx()
.err(format!(
"invalid contract `{}` specified in override list",
self.hir.contract(id).name.as_str()
))
.code(error_code!(2353))
.span(ov.span)
.note("contract is not a direct or indirect base")
.emit();
continue;
}
overrides.push(id);
}
self.arena.alloc_smallvec(overrides)
}
}
impl super::LoweringContext<'_> {
fn declare_kind_in(
&self,
scope: &mut Declarations,
name: Ident,
decl: Res,
) -> Result<(), ErrorGuaranteed> {
scope.declare_res(self.sess, &self.hir, name, decl)
}
fn declare_in(
&self,
scope: &mut Declarations,
name: Symbol,
decl: Declaration,
) -> Result<(), ErrorGuaranteed> {
scope.declare(self.sess, &self.hir, name, decl)
}
}
struct ResolverError {
name: Ident,
kind: ResolverErrorKind,
}
enum ResolverErrorKind {
Unresolved,
NotAScope(Res),
MultipleDeclarations,
}
impl ResolverError {
fn new(name: Ident, kind: ResolverErrorKind) -> Self {
Self { name, kind }
}
fn from_path(path: &ast::PathSlice, index: usize, kind: ResolverErrorKind) -> Self {
Self { name: path.segments()[index], kind }
}
fn span(&self) -> Span {
self.name.span
}
fn format(&self) -> String {
let name = self.name;
match self.kind {
ResolverErrorKind::Unresolved => format!("unresolved symbol `{name}`"),
ResolverErrorKind::NotAScope(kind) => {
format!(
"`{name}` is a {}, which cannot be indexed in type paths",
kind.description()
)
}
ResolverErrorKind::MultipleDeclarations => {
format!("symbol `{name}` resolved to multiple declarations")
}
}
}
}
#[derive(derive_more::Debug)]
pub(crate) struct SymbolResolver<'gcx> {
#[debug(ignore)]
dcx: &'gcx DiagCtxt,
pub(crate) source_scopes: IndexVec<hir::SourceId, Declarations>,
pub(crate) contract_scopes: IndexVec<hir::ContractId, Declarations>,
#[debug(ignore)]
global_builtin_scope: Declarations,
#[debug(ignore)]
builtin_members_scopes: FxHashMap<Builtin, Declarations>,
}
impl<'gcx> SymbolResolver<'gcx> {
pub(crate) fn new(dcx: &'gcx DiagCtxt) -> Self {
let (global_builtin_scope, builtin_members_scopes) = crate::builtins::scopes();
Self {
dcx,
source_scopes: IndexVec::new(),
contract_scopes: IndexVec::new(),
global_builtin_scope,
builtin_members_scopes,
}
}
fn resolve_path_as<T: TryFrom<Res>>(
&self,
path: &ast::PathSlice,
scopes: &SymbolResolverScopes,
description: &str,
) -> Result<T, ErrorGuaranteed> {
let decl = self.resolve_path(path, scopes).map_err(self.emit_resolver_error())?;
if let Res::Err(guar) = decl.res {
return Err(guar);
}
T::try_from(decl.res)
.map_err(|_| self.report_expected(description, decl.description(), path.span()))
}
fn emit_resolver_error(&self) -> impl Fn(ResolverError) -> ErrorGuaranteed + '_ {
move |e| self.dcx.emit_err(e.span(), e.format())
}
fn resolve_path(
&self,
path: &ast::PathSlice,
scopes: &SymbolResolverScopes,
) -> Result<Declaration, ResolverError> {
let decls = self.resolve_paths(path, scopes)?;
if let [decl] = decls {
Ok(*decl)
} else {
Err(ResolverError::new(*path.last(), ResolverErrorKind::MultipleDeclarations))
}
}
fn resolve_paths<'a>(
&'a self,
path: &ast::PathSlice,
scopes: &'a SymbolResolverScopes,
) -> Result<&'a [Declaration], ResolverError> {
let mut segments = path.segments().iter();
let name = *segments.next().unwrap();
let mut decls = self
.resolve_name_raw(name, scopes)
.ok_or_else(|| ResolverError::new(name, ResolverErrorKind::Unresolved))?;
for (prev_i, &segment) in segments.enumerate() {
let [decl] = decls else {
return Err(ResolverError::from_path(
path,
prev_i,
ResolverErrorKind::MultipleDeclarations,
));
};
if decl.res.is_err() {
return Ok(decls);
}
let scope = self.scope_of(decl.res).ok_or_else(|| {
ResolverError::from_path(path, prev_i, ResolverErrorKind::NotAScope(decl.res))
})?;
decls = scope.resolve(segment).ok_or_else(|| {
ResolverError::from_path(path, prev_i + 1, ResolverErrorKind::Unresolved)
})?;
}
Ok(decls)
}
fn resolve_name_raw<'a>(
&'a self,
name: Ident,
scopes: &'a SymbolResolverScopes,
) -> Option<&'a [Declaration]> {
scopes.get(self).find_map(move |scope| scope.resolve(name))
}
fn resolve_name_non_local<'a>(
&'a self,
name: Ident,
scopes: &'a SymbolResolverScopes,
) -> Result<&'a [Declaration], ResolverError> {
scopes
.get_non_local(self)
.find_map(move |scope| scope.resolve(name))
.ok_or_else(|| ResolverError::new(name, ResolverErrorKind::Unresolved))
}
fn scope_of(&self, declaration: Res) -> Option<&Declarations> {
match declaration {
Res::Item(hir::ItemId::Contract(id)) => Some(&self.contract_scopes[id]),
Res::Namespace(id) => Some(&self.source_scopes[id]),
Res::Builtin(builtin) => self.builtin_members_scopes.get(&builtin),
_ => None,
}
}
fn report_expected(&self, expected: &str, found: &str, span: Span) -> ErrorGuaranteed {
self.dcx.emit_err(span, format!("expected {expected}, found {found}"))
}
}
#[derive(Debug)]
pub(super) struct SymbolResolverScopes {
source: Option<hir::SourceId>,
contract: Option<hir::ContractId>,
scopes: Vec<Declarations>,
pool: Vec<Declarations>,
}
impl SymbolResolverScopes {
#[inline]
fn new() -> Self {
Self { source: None, contract: None, scopes: Vec::new(), pool: Vec::new() }
}
fn init(&mut self, source: hir::SourceId, contract: Option<hir::ContractId>) {
self.clear();
self.source = Some(source);
self.contract = contract;
}
fn clear(&mut self) {
self.pool.append(&mut self.scopes);
self.source = None;
self.contract = None;
}
#[inline]
fn get<'a>(
&'a self,
resolver: &'a SymbolResolver<'_>,
) -> impl Iterator<Item = &'a Declarations> + Clone + 'a {
debug_assert!(self.source.is_some() || self.contract.is_some());
self.scopes.iter().rev().chain(self.get_non_local(resolver))
}
#[inline]
fn get_non_local<'a>(
&'a self,
resolver: &'a SymbolResolver<'_>,
) -> impl Iterator<Item = &'a Declarations> + Clone + 'a {
self.contract
.map(|id| &resolver.contract_scopes[id])
.into_iter()
.chain(self.source.map(|id| &resolver.source_scopes[id]))
.chain(std::iter::once(&resolver.global_builtin_scope))
}
fn enter(&mut self) {
let mut scope = self.pool.pop().unwrap_or_else(Declarations::new);
scope.clear();
self.scopes.push(scope);
}
#[inline]
fn current_scope(&mut self) -> &mut Declarations {
if self.scopes.is_empty() {
self.enter();
}
self.scopes.last_mut().unwrap()
}
#[track_caller]
fn exit(&mut self) {
let scope = self.scopes.pop().expect("unbalanced enter/exit");
self.pool.push(scope);
}
}
pub(crate) struct Declarations {
declarations: FxIndexMap<Symbol, DeclarationsInner>,
}
impl fmt::Debug for Declarations {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Declarations ")?;
let mut map = f.debug_map();
for (key, values) in self.iter() {
map.entry(&key, if let [value] = values { value } else { &values });
}
map.finish()
}
}
const INNER_INLINE_CAPACITY: usize = 1;
const INNER_FIRST_RESERVE: usize = 4 - INNER_INLINE_CAPACITY;
type DeclarationsInner = SmallVec<[Declaration; INNER_INLINE_CAPACITY]>;
impl Declarations {
pub(crate) fn new() -> Self {
Self::with_capacity(0)
}
pub(crate) fn with_capacity(capacity: usize) -> Self {
Self { declarations: FxIndexMap::with_capacity_and_hasher(capacity, Default::default()) }
}
pub(crate) fn reserve(&mut self, additional: usize) {
self.declarations.reserve(additional);
}
pub(crate) fn clear(&mut self) {
self.declarations.clear();
}
pub(crate) fn len(&self) -> usize {
self.declarations.len()
}
pub(crate) fn iter(&self) -> impl Iterator<Item = (Symbol, &[Declaration])> {
self.declarations.iter().map(|(key, values)| (*key, values.as_slice()))
}
pub(crate) fn resolve(&self, name: Ident) -> Option<&[Declaration]> {
self.declarations.get(&name.name).map(std::ops::Deref::deref)
}
pub(crate) fn resolve_cloned(&self, name: Ident) -> Option<DeclarationsInner> {
self.declarations.get(&name.name).cloned()
}
pub(crate) fn declare_unchecked(&mut self, name: Symbol, decl: Declaration) {
self.declarations.entry(name).or_default().push(decl);
}
#[inline]
pub(crate) fn declare_res(
&mut self,
sess: &Session,
hir: &hir::Hir<'_>,
name: Ident,
res: Res,
) -> Result<(), ErrorGuaranteed> {
self.declare(sess, hir, name.name, Declaration { res, span: name.span })
}
pub(crate) fn declare(
&mut self,
sess: &Session,
hir: &hir::Hir<'_>,
name: Symbol,
decl: Declaration,
) -> Result<(), ErrorGuaranteed> {
self.try_declare(hir, name, decl)
.map_err(|conflict| report_conflict(hir, sess, name, decl, conflict))
}
pub(crate) fn try_declare(
&mut self,
hir: &hir::Hir<'_>,
name: Symbol,
decl: Declaration,
) -> Result<(), Declaration> {
match self.declarations.entry(name) {
IndexEntry::Occupied(entry) => {
let declarations = entry.into_mut();
if let Some(conflict) = Self::conflicting_declaration(hir, decl, declarations) {
return Err(conflict);
}
if !declarations.contains(&decl) {
if declarations.capacity() == INNER_INLINE_CAPACITY {
declarations.reserve(INNER_FIRST_RESERVE);
}
declarations.push(decl);
}
}
IndexEntry::Vacant(entry) => {
entry.insert(SmallVec::from_buf([decl]));
}
}
Ok(())
}
fn conflicting_declaration(
hir: &hir::Hir<'_>,
decl: Declaration,
declarations: &[Declaration],
) -> Option<Declaration> {
use Res::*;
use hir::ItemId::*;
if declarations.is_empty() {
return None;
}
if matches!(decl.res, Item(Function(_) | Event(_))) {
let mut getter = None;
if let Item(Function(id)) = decl.res {
getter = Some(id);
let f = hir.function(id);
if !f.kind.is_ordinary() {
return Some(declarations[0]);
}
}
let same_kind = |decl2: &Declaration| match decl2.res {
Item(Variable(v)) => hir.variable(v).getter == getter,
Item(Function(f)) => hir.function(f).kind.is_ordinary(),
ref k => k.matches(&decl.res),
};
declarations.iter().find(|&decl2| !same_kind(decl2)).copied()
} else if declarations == [decl] {
None
} else {
Some(declarations[0])
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Declaration {
pub(crate) res: Res,
pub(crate) span: Span,
}
impl std::ops::Deref for Declaration {
type Target = Res;
#[inline]
fn deref(&self) -> &Self::Target {
&self.res
}
}
impl PartialEq for Declaration {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.res == other.res
}
}
impl Eq for Declaration {}
pub(super) fn report_conflict(
hir: &hir::Hir<'_>,
sess: &Session,
name: Symbol,
decl: Declaration,
mut previous: Declaration,
) -> ErrorGuaranteed {
debug_assert_ne!(decl.span, previous.span);
let mut err = sess.dcx.err(format!("identifier `{name}` already declared")).span(decl.span);
if let Res::Item(item_id) = previous.res
&& let Ok(snippet) = sess.source_map().span_to_snippet(previous.span)
&& snippet.starts_with("import")
{
err = err.span_note(previous.span, "previous declaration imported here");
let real_span = hir.item(item_id).span();
previous.span = real_span;
}
if !previous.span.is_dummy() {
err = err.span_note(previous.span, "previous declaration declared here");
}
err.emit()
}
#[cfg(test)]
mod tests {
use crate::Compiler;
use solar_interface::{ColorChoice, Session};
use std::path::PathBuf;
#[test]
fn statement_variables_preserve_function_parent() {
let src = r#"
contract LocalParent {
function locals() public pure {
uint a = 1;
(uint b, uint c) = (2, 3);
}
}
"#;
let compiler = lower_source(src);
compiler.enter_sequential(|c| {
let gcx = c.gcx();
let locals = function_id(gcx, "LocalParent", "locals");
let local_parent = Some(crate::hir::ItemId::Function(locals));
let local_names: Vec<_> = gcx
.hir
.variables()
.filter(|v| v.kind == crate::hir::VarKind::Statement)
.filter_map(|v| v.name.map(|name| (name.name, v.parent)))
.collect();
for name in ["a", "b", "c"] {
assert!(
local_names.iter().any(|&(local, parent)| {
local.as_str() == name && parent == local_parent
}),
"local variable {name} did not preserve its function parent"
);
}
});
}
fn function_id(
gcx: crate::ty::Gcx<'_>,
contract_name: &str,
func_name: &str,
) -> crate::hir::FunctionId {
gcx.hir
.functions_enumerated()
.find(|(_, f)| {
f.contract.is_some_and(|cid| {
gcx.hir.contract(cid).name.as_str() == contract_name
&& f.name.is_some_and(|n| n.as_str() == func_name)
})
})
.map(|(id, _)| id)
.unwrap_or_else(|| panic!("{contract_name}.{func_name} not found"))
}
fn lower_source(src: &str) -> Compiler {
let sess =
Session::builder().with_buffer_emitter(ColorChoice::Never).single_threaded().build();
let mut compiler = Compiler::new(sess);
let _ = compiler.enter_mut(|compiler| -> solar_interface::Result<_> {
let mut parsing_context = compiler.parse();
let file = compiler
.sess()
.source_map()
.new_source_file(PathBuf::from("test.sol"), src.to_string())
.unwrap();
parsing_context.add_file(file);
parsing_context.parse();
let _ = compiler.lower_asts()?;
Ok(())
});
compiler
}
}