use crate::sema::ast::{
ArrayLength, Builtin, CallArgs, CallTy, Expression, ExternalCallAccounts, Function, Mutability,
Namespace, RetrieveType, StructType, Symbol, Type,
};
use crate::sema::contracts::is_base;
use crate::sema::diagnostics::Diagnostics;
use crate::sema::expression::constructor::{
deprecated_constructor_arguments, new, solana_constructor_check,
};
use crate::sema::expression::literals::{named_struct_literal, struct_literal};
use crate::sema::expression::resolve_expression::expression;
use crate::sema::expression::{ExprContext, ResolveTo};
use crate::sema::format::string_format;
use crate::sema::namespace::ResolveTypeContext;
use crate::sema::symtable::Symtable;
use crate::sema::unused_variable::check_function_call;
use crate::sema::{builtin, using};
use crate::Target;
use num_bigint::{BigInt, Sign};
use solang_parser::diagnostics::{Diagnostic, Note};
use solang_parser::pt;
use solang_parser::pt::{CodeLocation, Loc, Visibility};
use std::collections::{HashMap, HashSet};
pub(super) fn call_function_type(
loc: &pt::Loc,
expr: &pt::Expression,
args: &[pt::Expression],
call_args: &[&pt::NamedArgument],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
if context.constant {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call function in constant expression".to_string(),
));
return Err(());
}
let mut function = expression(expr, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
let mut ty = function.ty();
match ty {
Type::StorageRef(_, real_ty) | Type::Ref(real_ty) => {
ty = *real_ty;
function = function.cast(&expr.loc(), &ty, true, ns, diagnostics)?;
}
_ => (),
};
if let Type::InternalFunction {
params, returns, ..
} = ty
{
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not permitted for internal calls".to_string(),
));
}
if params.len() != args.len() {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"function expects {} arguments, {} provided",
params.len(),
args.len()
),
));
return Err(());
}
let mut cast_args = Vec::new();
for (i, arg) in args.iter().enumerate() {
let arg = expression(
arg,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(¶ms[i]),
)?;
cast_args.push(arg.cast(&arg.loc(), ¶ms[i], true, ns, diagnostics)?);
}
Ok(Expression::InternalFunctionCall {
loc: *loc,
returns: if returns.is_empty() || resolve_to == ResolveTo::Discard {
vec![Type::Void]
} else {
returns
},
function: Box::new(function),
args: cast_args,
})
} else if let Type::ExternalFunction {
returns,
params,
mutability,
} = ty
{
let call_args = parse_call_args(
loc,
call_args,
None,
true,
context,
ns,
symtable,
diagnostics,
)?;
if let Some(value) = &call_args.value {
if !value.const_zero(ns) && !matches!(mutability, Mutability::Payable(_)) {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"sending value to function type '{}' which is not payable",
function.ty().to_string(ns),
),
));
return Err(());
}
}
if params.len() != args.len() {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"function expects {} arguments, {} provided",
params.len(),
args.len()
),
));
return Err(());
}
let mut cast_args = Vec::new();
for (i, arg) in args.iter().enumerate() {
let arg = expression(
arg,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(¶ms[i]),
)?;
cast_args.push(arg.cast(&arg.loc(), ¶ms[i], true, ns, diagnostics)?);
}
Ok(Expression::ExternalFunctionCall {
loc: *loc,
returns: if returns.is_empty() || resolve_to == ResolveTo::Discard {
vec![Type::Void]
} else {
returns
},
function: Box::new(function),
args: cast_args,
call_args,
})
} else {
diagnostics.push(Diagnostic::error(
*loc,
"expression is not a function".to_string(),
));
Err(())
}
}
pub fn available_functions(
name: &str,
global: bool,
file_no: usize,
contract_no: Option<usize>,
ns: &Namespace,
) -> Vec<usize> {
let mut list = Vec::new();
if let Some(contract_no) = contract_no {
list.extend(
ns.contracts[contract_no]
.all_functions
.keys()
.filter(|func_no| ns.functions[**func_no].id.name == name)
.filter_map(|func_no| {
let func = &ns.functions[*func_no];
if func.is_virtual
&& ns.contracts[contract_no].virtual_functions[&func.signature].last()
!= Some(func_no)
{
return None;
}
let is_abstract = func.is_virtual && !ns.contracts[contract_no].is_concrete();
if func.has_body || is_abstract {
return Some(*func_no);
}
None
}),
);
}
if list.is_empty() && global {
if let Some(Symbol::Function(v)) =
ns.function_symbols.get(&(file_no, None, name.to_owned()))
{
list.extend(v.iter().map(|(_, func_no)| *func_no));
}
}
list
}
pub fn available_super_functions(name: &str, contract_no: usize, ns: &Namespace) -> Vec<usize> {
let mut list = Vec::new();
let mut signatures = HashSet::new();
for base_contract_no in ns.contract_bases(contract_no).into_iter().rev() {
if base_contract_no == contract_no {
continue;
}
list.extend(
ns.contracts[base_contract_no]
.all_functions
.keys()
.filter_map(|func_no| {
let func = &ns.functions[*func_no];
if func.id.name == name && func.has_body {
if func.is_virtual && !signatures.insert(&func.signature) {
None
} else {
Some(*func_no)
}
} else {
None
}
}),
);
}
list
}
pub fn function_call_pos_args(
loc: &pt::Loc,
id: &pt::IdentifierPath,
func_ty: pt::FunctionTy,
args: &[pt::Expression],
mut function_nos: Vec<usize>,
virtual_call: bool,
context: &mut ExprContext,
ns: &mut Namespace,
resolve_to: ResolveTo,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
) -> Result<Expression, ()> {
if id.identifiers.len() == 1 && id.identifiers[0].name == "type" {
match args.len() {
0 => {
diagnostics.push(Diagnostic::error(
*loc,
"missing type argument to type() operator".to_string(),
));
return Err(());
}
1 => (),
_ => {
diagnostics.push(Diagnostic::error(
*loc,
"type() operator takes a single argument".to_string(),
));
return Err(());
}
}
let ty = ns.resolve_type(
context.file_no,
context.contract_no,
ResolveTypeContext::FunctionType,
&args[0],
diagnostics,
)?;
return Ok(Expression::TypeOperator { loc: *loc, ty });
}
if context.constant {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call function in constant expression".to_string(),
));
return Err(());
}
if args.iter().fold(false, |acc, arg| {
acc | expression(arg, context, ns, symtable, diagnostics, ResolveTo::Unknown).is_err()
}) {
return Err(());
}
function_nos.retain(|function_no| ns.functions[*function_no].ty == func_ty);
let mut call_diagnostics = Diagnostics::default();
let mut resolved_calls = Vec::new();
for function_no in &function_nos {
let func = &ns.functions[*function_no];
let mut candidate_diagnostics = Diagnostics::default();
let mut cast_args = Vec::new();
if func.params.len() != args.len() {
candidate_diagnostics.push(Diagnostic::error(
*loc,
format!(
"{} expects {} arguments, {} provided",
func.ty,
func.params.len(),
args.len()
),
));
} else {
for (i, arg) in args.iter().enumerate() {
let ty = ns.functions[*function_no].params[i].ty.clone();
evaluate_argument(
arg,
context,
ns,
symtable,
&ty,
&mut candidate_diagnostics,
&mut cast_args,
);
}
}
if candidate_diagnostics.any_errors() {
if function_nos.len() != 1 {
candidate_diagnostics.push(Diagnostic::error(
*loc,
format!("cannot find overloaded {func_ty} which matches signature"),
));
let func = &ns.functions[*function_no];
candidate_diagnostics.iter_mut().for_each(|diagnostic| {
diagnostic.notes.push(Note {
loc: func.loc,
message: "candidate function".into(),
})
});
}
} else if let Some(resolved_call) = resolve_internal_call(
loc,
id,
*function_no,
context,
resolve_to,
virtual_call,
cast_args,
ns,
&mut candidate_diagnostics,
) {
resolved_calls.push((*function_no, resolved_call));
continue;
}
call_diagnostics.extend(candidate_diagnostics);
}
let id = id.identifiers.last().unwrap();
match resolved_calls.len() {
0 => {
diagnostics.extend(call_diagnostics);
if function_nos.is_empty() {
if func_ty == pt::FunctionTy::Modifier {
diagnostics.push(Diagnostic::error(
id.loc,
format!("unknown modifier '{}'", id.name),
));
} else {
diagnostics.push(Diagnostic::error(
id.loc,
format!("unknown {} or type '{}'", func_ty, id.name),
));
}
}
Err(())
}
1 => Ok(resolved_calls[0].1.clone()),
_ => {
diagnostics.push(Diagnostic::error_with_notes(
*loc,
"function call can be resolved to multiple functions".into(),
resolved_calls
.iter()
.map(|(func_no, _)| {
let func = &ns.functions[*func_no];
Note {
loc: func.loc,
message: "candidate function".into(),
}
})
.collect(),
));
Err(())
}
}
}
pub(super) fn function_call_named_args(
loc: &pt::Loc,
id: &pt::IdentifierPath,
args: &[pt::NamedArgument],
mut function_nos: Vec<usize>,
virtual_call: bool,
context: &mut ExprContext,
resolve_to: ResolveTo,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
) -> Result<Expression, ()> {
let mut arguments = HashMap::new();
if args.iter().fold(false, |mut acc, arg| {
if arguments.contains_key(arg.name.name.as_str()) {
diagnostics.push(Diagnostic::error(
arg.name.loc,
format!("duplicate argument with name '{}'", arg.name.name),
));
let _ = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
);
} else {
acc |= expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
)
.is_err()
}
arguments.insert(arg.name.name.as_str(), &arg.expr);
acc
}) {
return Err(());
}
function_nos.retain(|function_no| ns.functions[*function_no].ty == pt::FunctionTy::Function);
let mut call_diagnostics = Diagnostics::default();
let mut resolved_calls = Vec::new();
for function_no in &function_nos {
let func = &ns.functions[*function_no];
let mut candidate_diagnostics = Diagnostics::default();
let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
let params_len = func.params.len();
let mut cast_args = Vec::new();
if unnamed_params > 0 {
candidate_diagnostics.push(Diagnostic::cast_error_with_note(
*loc,
format!(
"function cannot be called with named arguments as {unnamed_params} of its parameters do not have names"
),
func.loc_prototype,
format!("definition of {}", func.id),
));
} else {
if params_len != args.len() {
candidate_diagnostics.push(Diagnostic::cast_error(
*loc,
format!(
"function expects {} arguments, {} provided",
params_len,
args.len()
),
));
}
for i in 0..params_len {
let param = &ns.functions[*function_no].params[i];
if param.id.is_none() {
continue;
}
let arg = match arguments.get(param.name_as_str()) {
Some(a) => a,
None => {
candidate_diagnostics.push(Diagnostic::cast_error(
*loc,
format!(
"missing argument '{}' to function '{}'",
param.name_as_str(),
id,
),
));
continue;
}
};
let ty = param.ty.clone();
evaluate_argument(
arg,
context,
ns,
symtable,
&ty,
&mut candidate_diagnostics,
&mut cast_args,
);
}
}
if candidate_diagnostics.any_errors() {
if function_nos.len() != 1 {
candidate_diagnostics.push(Diagnostic::error(
*loc,
"cannot find overloaded function which matches signature".into(),
));
let func = &ns.functions[*function_no];
candidate_diagnostics.iter_mut().for_each(|diagnostic| {
diagnostic.notes.push(Note {
loc: func.loc,
message: "candidate function".into(),
})
});
}
} else if let Some(resolved_call) = resolve_internal_call(
loc,
id,
*function_no,
context,
resolve_to,
virtual_call,
cast_args,
ns,
&mut candidate_diagnostics,
) {
resolved_calls.push((*function_no, resolved_call));
continue;
}
call_diagnostics.extend(candidate_diagnostics);
}
match resolved_calls.len() {
0 => {
diagnostics.extend(call_diagnostics);
if function_nos.is_empty() {
let id = id.identifiers.last().unwrap();
diagnostics.push(Diagnostic::error(
id.loc,
format!("unknown function or type '{}'", id.name),
));
}
Err(())
}
1 => Ok(resolved_calls[0].1.clone()),
_ => {
diagnostics.push(Diagnostic::error_with_notes(
*loc,
"function call can be resolved to multiple functions".into(),
resolved_calls
.iter()
.map(|(func_no, _)| {
let func = &ns.functions[*func_no];
Note {
loc: func.loc,
message: "candidate function".into(),
}
})
.collect(),
));
Err(())
}
}
}
fn try_namespace(
loc: &pt::Loc,
var: &pt::Expression,
func: &pt::Identifier,
args: &[pt::Expression],
call_args: &[&pt::NamedArgument],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Option<Expression>, ()> {
let namespace = match var {
pt::Expression::Variable(namespace) => Some(namespace.clone()),
pt::Expression::Type(loc, pt::Type::String) => Some(pt::Identifier {
name: "string".to_owned(),
loc: *loc,
}),
pt::Expression::Type(loc, pt::Type::DynamicBytes) => Some(pt::Identifier {
name: "bytes".to_owned(),
loc: *loc,
}),
_ => None,
};
if let Some(namespace) = &namespace {
if builtin::is_builtin_call(Some(&namespace.name), &func.name, ns) {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on builtins".to_string(),
));
return Err(());
}
return Ok(Some(builtin::resolve_namespace_call(
loc,
&namespace.name,
&func.name,
args,
context,
ns,
symtable,
diagnostics,
)?));
}
let id_path = pt::IdentifierPath {
loc: *loc,
identifiers: vec![namespace.clone(), func.clone()],
};
if namespace.name == "super" {
if let Some(cur_contract_no) = context.contract_no {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on super calls".to_string(),
));
return Err(());
}
return Ok(Some(function_call_pos_args(
loc,
&id_path,
pt::FunctionTy::Function,
args,
available_super_functions(&func.name, cur_contract_no, ns),
false,
context,
ns,
resolve_to,
symtable,
diagnostics,
)?));
} else {
diagnostics.push(Diagnostic::error(
*loc,
"super not available outside contracts".to_string(),
));
return Err(());
}
}
if let Some(call_contract_no) = ns.resolve_contract(context.file_no, namespace) {
if ns.contracts[call_contract_no].is_library() {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on library calls".to_string(),
));
return Err(());
}
return Ok(Some(function_call_pos_args(
loc,
&id_path,
pt::FunctionTy::Function,
args,
available_functions(
&func.name,
false,
context.file_no,
Some(call_contract_no),
ns,
),
true,
context,
ns,
resolve_to,
symtable,
diagnostics,
)?));
}
if let Some(contract_no) = context.contract_no {
if is_base(call_contract_no, contract_no, ns) {
if ns.target == Target::Solana && call_args_loc.is_some() {
return contract_call_pos_args(
loc,
call_contract_no,
func,
None,
args,
call_args,
context,
ns,
symtable,
diagnostics,
resolve_to,
);
} else if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on internal calls".to_string(),
));
return Err(());
}
return Ok(Some(function_call_pos_args(
loc,
&id_path,
pt::FunctionTy::Function,
args,
available_functions(
&func.name,
false,
context.file_no,
Some(call_contract_no),
ns,
),
false,
context,
ns,
resolve_to,
symtable,
diagnostics,
)?));
} else if ns.target != Target::Solana {
diagnostics.push(Diagnostic::error(
*loc,
"function calls via contract name are only valid for base contracts".into(),
));
}
}
if ns.target == Target::Solana {
return contract_call_pos_args(
loc,
call_contract_no,
func,
None,
args,
call_args,
context,
ns,
symtable,
diagnostics,
resolve_to,
);
}
}
}
Ok(None)
}
fn try_storage_reference(
loc: &pt::Loc,
var_expr: &Expression,
func: &pt::Identifier,
args: &[pt::Expression],
context: &mut ExprContext,
diagnostics: &mut Diagnostics,
call_args_loc: Option<pt::Loc>,
ns: &mut Namespace,
symtable: &mut Symtable,
resolve_to: &ResolveTo,
) -> Result<Option<Expression>, ()> {
if let Type::StorageRef(immutable, ty) = &var_expr.ty() {
match ty.as_ref() {
Type::Array(_, dim) => {
if *immutable {
if let Some(function_no) = context.function_no {
if !ns.functions[function_no].is_constructor() {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call method on immutable array outside of constructor"
.to_string(),
));
return Err(());
}
}
}
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on arrays".to_string(),
));
return Err(());
}
if func.name == "push" {
if matches!(dim.last(), Some(ArrayLength::Fixed(_))) {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'push()' not allowed on fixed length array".to_string(),
));
return Err(());
}
let elem_ty = ty.array_elem();
let mut builtin_args = vec![var_expr.clone()];
let ret_ty = match args.len() {
1 => {
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&elem_ty),
)?;
builtin_args.push(expr.cast(
&args[0].loc(),
&elem_ty,
true,
ns,
diagnostics,
)?);
Type::Void
}
0 => {
if elem_ty.is_reference_type(ns) {
Type::StorageRef(false, Box::new(elem_ty))
} else {
elem_ty
}
}
_ => {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'push()' takes at most 1 argument".to_string(),
));
return Err(());
}
};
return Ok(Some(Expression::Builtin {
loc: func.loc,
tys: vec![ret_ty],
kind: Builtin::ArrayPush,
args: builtin_args,
}));
}
if func.name == "pop" {
if matches!(dim.last(), Some(ArrayLength::Fixed(_))) {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'pop()' not allowed on fixed length array".to_string(),
));
return Err(());
}
if !args.is_empty() {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'pop()' does not take any arguments".to_string(),
));
return Err(());
}
let storage_elem = ty.storage_array_elem();
let elem_ty = storage_elem.deref_any();
let return_ty = if *resolve_to == ResolveTo::Discard {
Type::Void
} else {
elem_ty.clone()
};
return Ok(Some(Expression::Builtin {
loc: func.loc,
tys: vec![return_ty],
kind: Builtin::ArrayPop,
args: vec![var_expr.clone()],
}));
}
}
Type::DynamicBytes => {
if *immutable {
if let Some(function_no) = context.function_no {
if !ns.functions[function_no].is_constructor() {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call method on immutable bytes outside of constructor"
.to_string(),
));
return Err(());
}
}
}
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on bytes".to_string(),
));
return Err(());
}
if func.name == "push" {
let mut builtin_args = vec![var_expr.clone()];
let elem_ty = Type::Bytes(1);
let ret_ty = match args.len() {
1 => {
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&elem_ty),
)?;
builtin_args.push(expr.cast(
&args[0].loc(),
&elem_ty,
true,
ns,
diagnostics,
)?);
Type::Void
}
0 => elem_ty,
_ => {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'push()' takes at most 1 argument".to_string(),
));
return Err(());
}
};
return Ok(Some(Expression::Builtin {
loc: func.loc,
tys: vec![ret_ty],
kind: Builtin::ArrayPush,
args: builtin_args,
}));
}
if func.name == "pop" {
if !args.is_empty() {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'pop()' does not take any arguments".to_string(),
));
return Err(());
}
return Ok(Some(Expression::Builtin {
loc: func.loc,
tys: vec![Type::Bytes(1)],
kind: Builtin::ArrayPop,
args: vec![var_expr.clone()],
}));
}
}
_ => {}
}
}
Ok(None)
}
fn try_user_type(
loc: &pt::Loc,
var: &pt::Expression,
func: &pt::Identifier,
args: &[pt::Expression],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
) -> Result<Option<Expression>, ()> {
if let Ok(Type::UserType(no)) = ns.resolve_type(
context.file_no,
context.contract_no,
ResolveTypeContext::None,
var,
&mut Diagnostics::default(),
) {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on builtins".to_string(),
));
return Err(());
}
let elem_ty = ns.user_types[no].ty.clone();
let user_ty = Type::UserType(no);
if func.name == "unwrap" {
return if args.len() != 1 {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'unwrap()' takes one argument".to_string(),
));
Err(())
} else {
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&user_ty),
)?;
Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![elem_ty],
kind: Builtin::UserTypeUnwrap,
args: vec![expr.cast(&expr.loc(), &user_ty, true, ns, diagnostics)?],
}))
};
} else if func.name == "wrap" {
return if args.len() != 1 {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'wrap()' takes one argument".to_string(),
));
Err(())
} else {
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&elem_ty),
)?;
Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![user_ty],
kind: Builtin::UserTypeWrap,
args: vec![expr.cast(&expr.loc(), &elem_ty, true, ns, diagnostics)?],
}))
};
}
}
Ok(None)
}
fn try_type_method(
loc: &pt::Loc,
func: &pt::Identifier,
var: &pt::Expression,
args: &[pt::Expression],
call_args: &[&pt::NamedArgument],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
var_expr: &Expression,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Option<Expression>, ()> {
let var_ty = var_expr.ty();
match var_ty.deref_any() {
Type::Bytes(..) | Type::String if func.name == "format" => {
return if let pt::Expression::StringLiteral(bs) = var {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on builtins".to_string(),
));
return Err(());
}
Ok(Some(string_format(
loc,
bs,
args,
context,
ns,
symtable,
diagnostics,
)?))
} else {
diagnostics.push(Diagnostic::error(
*loc,
"format only allowed on string literals".to_string(),
));
Err(())
};
}
Type::Array(..) | Type::DynamicBytes if var_ty.is_dynamic(ns) => {
if func.name == "push" {
let elem_ty = var_ty.array_elem();
let val = match args.len() {
0 => {
return Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![elem_ty.clone()],
kind: Builtin::ArrayPush,
args: vec![var_expr.clone()],
}));
}
1 => {
let val_expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&elem_ty),
)?;
val_expr.cast(&args[0].loc(), &elem_ty, true, ns, diagnostics)?
}
_ => {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'push()' takes at most 1 argument".to_string(),
));
return Err(());
}
};
return Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![elem_ty.clone()],
kind: Builtin::ArrayPush,
args: vec![var_expr.clone(), val],
}));
}
if func.name == "pop" {
if !args.is_empty() {
diagnostics.push(Diagnostic::error(
func.loc,
"method 'pop()' does not take any arguments".to_string(),
));
return Err(());
}
let elem_ty = match &var_ty {
Type::Array(ty, _) => ty,
Type::DynamicBytes => &Type::Uint(8),
_ => unreachable!(),
};
return Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![elem_ty.clone()],
kind: Builtin::ArrayPop,
args: vec![var_expr.clone()],
}));
}
}
Type::Array(..) if func.name == "push" || func.name == "pop" => {
diagnostics.push(Diagnostic::error(
func.loc,
format!(
"method {}() is not available for fixed length arrays",
func.name
),
));
return Err(());
}
Type::Contract(ext_contract_no) => {
return contract_call_pos_args(
loc,
*ext_contract_no,
func,
Some(var_expr),
args,
call_args,
context,
ns,
symtable,
diagnostics,
resolve_to,
);
}
Type::Address(is_payable) => {
if func.name == "transfer" || func.name == "send" {
if ns.target == Target::Solana {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"method '{}' not available on Solana. Use the lamports \
field from the AccountInfo struct directly to operate on balances.",
func.name
),
));
return Err(());
}
if !is_payable {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"method '{}' available on type 'address payable' not 'address'",
func.name,
),
));
return Err(());
}
if args.len() != 1 {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"'{}' expects 1 argument, {} provided",
func.name,
args.len()
),
));
return Err(());
}
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
format!("call arguments not allowed on '{}'", func.name),
));
return Err(());
}
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&Type::Value),
)?;
let address =
var_expr.cast(&var_expr.loc(), var_ty.deref_any(), true, ns, diagnostics)?;
let value = expr.cast(&args[0].loc(), &Type::Value, true, ns, diagnostics)?;
return if func.name == "transfer" {
Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![Type::Void],
kind: Builtin::PayableTransfer,
args: vec![address, value],
}))
} else {
Ok(Some(Expression::Builtin {
loc: *loc,
tys: vec![Type::Bool],
kind: Builtin::PayableSend,
args: vec![address, value],
}))
};
}
let ty = match func.name.as_str() {
"call" => Some(CallTy::Regular),
"delegatecall" if ns.target != Target::Solana => Some(CallTy::Delegate),
"staticcall" if ns.target == Target::EVM => Some(CallTy::Static),
_ => None,
};
if let Some(ty) = ty {
let call_args = parse_call_args(
loc,
call_args,
None,
true,
context,
ns,
symtable,
diagnostics,
)?;
if ty != CallTy::Regular && call_args.value.is_some() {
diagnostics.push(Diagnostic::error(
*loc,
format!("'{}' cannot have value specified", func.name,),
));
return Err(());
}
if ty == CallTy::Delegate && ns.target.is_polkadot() && call_args.gas.is_some() {
diagnostics.push(Diagnostic::warning(
*loc,
"'gas' specified on 'delegatecall' will be ignored".into(),
));
}
if args.len() != 1 {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"'{}' expects 1 argument, {} provided",
func.name,
args.len()
),
));
return Err(());
}
let args = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&Type::DynamicBytes),
)?;
let mut args_ty = args.ty();
match args_ty.deref_any() {
Type::DynamicBytes => (),
Type::Bytes(_) => {
args_ty = Type::DynamicBytes;
}
Type::Array(..) | Type::Struct(..) if !args_ty.is_dynamic(ns) => {}
_ => {
diagnostics.push(Diagnostic::error(
args.loc(),
format!("'{}' is not fixed length type", args_ty.to_string(ns),),
));
return Err(());
}
}
let args = args.cast(&args.loc(), args_ty.deref_any(), true, ns, diagnostics)?;
return Ok(Some(Expression::ExternalFunctionCallRaw {
loc: *loc,
ty,
args: Box::new(args),
address: Box::new(var_expr.cast(
&var_expr.loc(),
&Type::Address(*is_payable),
true,
ns,
diagnostics,
)?),
call_args,
}));
}
}
_ => (),
}
Ok(None)
}
pub(super) fn method_call_pos_args(
loc: &pt::Loc,
var: &pt::Expression,
func: &pt::Identifier,
args: &[pt::Expression],
call_args: &[&pt::NamedArgument],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
if let Some(resolved_call) = try_namespace(
loc,
var,
func,
args,
call_args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
resolve_to,
)? {
return Ok(resolved_call);
}
if let Some(resolved_call) = try_user_type(
loc,
var,
func,
args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
)? {
return Ok(resolved_call);
}
if context.constant {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call function in constant expression".to_string(),
));
return Err(());
}
if let Some(mut path) = ns.expr_to_identifier_path(var) {
path.identifiers.push(func.clone());
if let Ok(list) = ns.resolve_function_with_namespace(
context.file_no,
None,
&path,
&mut Diagnostics::default(),
) {
if let Some(callee_contract) =
is_solana_external_call(&list, context.contract_no, &call_args_loc, ns)
{
if let Some(resolved_call) = contract_call_pos_args(
&var.loc(),
callee_contract,
func,
None,
args,
call_args,
context,
ns,
symtable,
diagnostics,
resolve_to,
)? {
return Ok(resolved_call);
}
}
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on internal calls".to_string(),
));
}
return function_call_pos_args(
loc,
&path,
pt::FunctionTy::Function,
args,
list.iter().map(|(_, no)| *no).collect(),
false,
context,
ns,
resolve_to,
symtable,
diagnostics,
);
}
}
let var_expr = expression(var, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
if let Some(resolved_call) =
builtin::resolve_method_call(&var_expr, func, args, context, ns, symtable, diagnostics)?
{
return Ok(resolved_call);
}
if let Some(resolved_call) = try_storage_reference(
loc,
&var_expr,
func,
args,
context,
diagnostics,
call_args_loc,
ns,
symtable,
&resolve_to,
)? {
return Ok(resolved_call);
}
let mut diagnostics_type: u8 = 0;
let mut type_method_diagnostics = Diagnostics::default();
match try_type_method(
loc,
func,
var,
args,
call_args,
call_args_loc,
context,
&var_expr,
ns,
symtable,
&mut type_method_diagnostics,
resolve_to,
) {
Ok(Some(resolved_call)) => {
diagnostics.extend(type_method_diagnostics);
return Ok(resolved_call);
}
Ok(None) => (),
Err(()) => {
diagnostics_type += 1;
}
}
let mut resolve_using_diagnostics = Diagnostics::default();
match using::try_resolve_using_call(
loc,
func,
&var_expr,
context,
args,
symtable,
&mut resolve_using_diagnostics,
ns,
resolve_to,
) {
Ok(Some(resolved_call)) => {
diagnostics.extend(resolve_using_diagnostics);
return Ok(resolved_call);
}
Ok(None) => (),
Err(()) => {
diagnostics_type += 2;
}
}
match diagnostics_type {
1 => diagnostics.extend(type_method_diagnostics),
2 => diagnostics.extend(resolve_using_diagnostics),
_ => diagnostics.push(Diagnostic::error(
func.loc,
format!("method '{}' does not exist", func.name),
)),
}
Err(())
}
pub(super) fn method_call_named_args(
loc: &pt::Loc,
var: &pt::Expression,
func_name: &pt::Identifier,
args: &[pt::NamedArgument],
call_args: &[&pt::NamedArgument],
call_args_loc: Option<pt::Loc>,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
if let pt::Expression::Variable(namespace) = var {
let id_path = pt::IdentifierPath {
loc: *loc,
identifiers: vec![namespace.clone(), func_name.clone()],
};
if namespace.name == "super" {
if let Some(cur_contract_no) = context.contract_no {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on super calls".to_string(),
));
return Err(());
}
return function_call_named_args(
loc,
&id_path,
args,
available_super_functions(&func_name.name, cur_contract_no, ns),
false,
context,
resolve_to,
ns,
symtable,
diagnostics,
);
} else {
diagnostics.push(Diagnostic::error(
*loc,
"super not available outside contracts".to_string(),
));
return Err(());
}
}
if let Some(call_contract_no) = ns.resolve_contract(context.file_no, namespace) {
if ns.contracts[call_contract_no].is_library() {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on library calls".to_string(),
));
return Err(());
}
return function_call_named_args(
loc,
&id_path,
args,
available_functions(
&func_name.name,
false,
context.file_no,
Some(call_contract_no),
ns,
),
true,
context,
resolve_to,
ns,
symtable,
diagnostics,
);
}
if let Some(contract_no) = context.contract_no {
if is_base(call_contract_no, contract_no, ns) {
if ns.target == Target::Solana && call_args_loc.is_some() {
return contract_call_named_args(
loc,
None,
func_name,
args,
call_args,
call_contract_no,
context,
symtable,
ns,
diagnostics,
resolve_to,
);
} else if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on internal calls".to_string(),
));
return Err(());
}
return function_call_named_args(
loc,
&id_path,
args,
available_functions(
&func_name.name,
false,
context.file_no,
Some(call_contract_no),
ns,
),
false,
context,
resolve_to,
ns,
symtable,
diagnostics,
);
} else if ns.target != Target::Solana {
diagnostics.push(Diagnostic::error(
*loc,
"function calls via contract name are only valid for base contracts".into(),
));
}
}
if ns.target == Target::Solana {
return contract_call_named_args(
loc,
None,
func_name,
args,
call_args,
call_contract_no,
context,
symtable,
ns,
diagnostics,
resolve_to,
);
}
}
}
if let Some(mut path) = ns.expr_to_identifier_path(var) {
path.identifiers.push(func_name.clone());
if let Ok(list) = ns.resolve_function_with_namespace(
context.file_no,
None,
&path,
&mut Diagnostics::default(),
) {
if let Some(callee_contract) =
is_solana_external_call(&list, context.contract_no, &call_args_loc, ns)
{
return contract_call_named_args(
&var.loc(),
None,
func_name,
args,
call_args,
callee_contract,
context,
symtable,
ns,
diagnostics,
resolve_to,
);
}
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not allowed on internal calls".to_string(),
));
}
return function_call_named_args(
loc,
&path,
args,
list.iter().map(|(_, no)| *no).collect(),
false,
context,
resolve_to,
ns,
symtable,
diagnostics,
);
}
}
let var_expr = expression(var, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
let var_ty = var_expr.ty();
if let Type::Contract(external_contract_no) = &var_ty.deref_any() {
return contract_call_named_args(
loc,
Some(var_expr),
func_name,
args,
call_args,
*external_contract_no,
context,
symtable,
ns,
diagnostics,
resolve_to,
);
}
diagnostics.push(Diagnostic::error(
func_name.loc,
format!("method '{}' does not exist", func_name.name),
));
Err(())
}
pub fn collect_call_args<'a>(
expr: &'a pt::Expression,
diagnostics: &mut Diagnostics,
) -> Result<
(
&'a pt::Expression,
Vec<&'a pt::NamedArgument>,
Option<pt::Loc>,
),
(),
> {
let mut named_arguments = Vec::new();
let mut expr = expr;
let mut loc: Option<pt::Loc> = None;
while let pt::Expression::FunctionCallBlock(_, e, block) = expr {
match block.as_ref() {
pt::Statement::Args(_, args) => {
if let Some(pt::Loc::File(file_no, start, _)) = loc {
loc = Some(pt::Loc::File(file_no, start, block.loc().end()));
} else {
loc = Some(block.loc());
}
named_arguments.extend(args);
}
pt::Statement::Block { statements, .. } if statements.is_empty() => {
diagnostics.push(Diagnostic::error(
block.loc(),
"missing call arguments".to_string(),
));
return Err(());
}
_ => {
diagnostics.push(Diagnostic::error(
block.loc(),
"code block found where list of call arguments expected, like '{gas: 5000}'"
.to_string(),
));
return Err(());
}
}
expr = e;
}
Ok((expr, named_arguments, loc))
}
pub(super) fn parse_call_args(
loc: &pt::Loc,
call_args: &[&pt::NamedArgument],
callee_contract: Option<usize>,
external_call: bool,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
) -> Result<CallArgs, ()> {
let mut args: HashMap<&String, &pt::NamedArgument> = HashMap::new();
for arg in call_args {
if let Some(prev) = args.get(&arg.name.name) {
diagnostics.push(Diagnostic::error_with_note(
arg.loc,
format!("'{}' specified multiple times", arg.name.name),
prev.loc,
format!("location of previous declaration of '{}'", arg.name.name),
));
return Err(());
}
args.insert(&arg.name.name, arg);
}
let mut res = CallArgs::default();
for arg in args.values() {
match arg.name.name.as_str() {
"value" => {
if ns.target == Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
"Solana Cross Program Invocation (CPI) cannot transfer native value. See https://solang.readthedocs.io/en/latest/language/functions.html#value_transfer".to_string(),
));
expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
)?;
} else {
let ty = Type::Value;
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
res.value = Some(Box::new(expr.cast(
&arg.expr.loc(),
&ty,
true,
ns,
diagnostics,
)?));
}
}
"gas" => {
if ns.target == Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'gas' not permitted for external calls or constructors on {}",
ns.target
),
));
return Err(());
}
let ty = Type::Uint(64);
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
res.gas = Some(Box::new(expr.cast(
&arg.expr.loc(),
&ty,
true,
ns,
diagnostics,
)?));
}
"salt" => {
if ns.target == Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'salt' not permitted for external calls or constructors on {}",
ns.target
),
));
return Err(());
}
if external_call {
diagnostics.push(Diagnostic::error(
arg.loc,
"'salt' not valid for external calls".to_string(),
));
return Err(());
}
let ty = Type::Bytes(32);
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
res.salt = Some(Box::new(expr.cast(
&arg.expr.loc(),
&ty,
true,
ns,
diagnostics,
)?));
}
"accounts" => {
if ns.target != Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'accounts' not permitted for external calls or constructors on {}",
ns.target
),
));
return Err(());
}
if let pt::Expression::ArrayLiteral(_, vec) = &arg.expr {
if vec.is_empty() {
res.accounts = ExternalCallAccounts::NoAccount;
continue;
}
}
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
)?;
let mut correct_ty = false;
let expr_ty = expr.ty();
if let Type::Array(elem_ty, dims) = expr_ty.deref_memory() {
if elem_ty.is_builtin_struct() == Some(StructType::AccountMeta)
&& dims.len() == 1
{
correct_ty = true;
}
}
if !correct_ty {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'accounts' takes array of AccountMeta, not '{}'",
expr_ty.to_string(ns)
),
));
return Err(());
} else if expr_ty.is_dynamic(ns) {
diagnostics.push(Diagnostic::error(
arg.loc,
"dynamic array is not supported for the 'accounts' argument".to_string(),
));
}
res.accounts = ExternalCallAccounts::Present(Box::new(expr));
}
"seeds" => {
if ns.target != Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'seeds' not permitted for external calls or constructors on {}",
ns.target
),
));
return Err(());
}
let ty = Type::Slice(Type::Slice(Type::Slice(Type::Bytes(1).into()).into()).into());
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
res.seeds = Some(expr.cast(&expr.loc(), &ty, true, ns, diagnostics)?.into());
}
"program_id" => {
if ns.target != Target::Solana {
diagnostics.push(Diagnostic::error(
arg.loc,
format!(
"'program_id' not permitted for external calls or constructors on {}",
ns.target
),
));
return Err(());
}
let ty = Type::Address(false);
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
res.program_id = Some(Box::new(expr));
}
"flags" => {
if !(ns.target.is_polkadot() && external_call) {
diagnostics.push(Diagnostic::error(
arg.loc,
"'flags' are only permitted for external calls on polkadot".into(),
));
return Err(());
}
let ty = Type::Uint(32);
let expr = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Type(&ty),
)?;
let flags = expr.cast(&arg.expr.loc(), &ty, true, ns, diagnostics)?;
res.flags = Some(flags.into());
}
_ => {
diagnostics.push(Diagnostic::error(
arg.loc,
format!("'{}' not a valid call parameter", arg.name.name),
));
return Err(());
}
}
}
if ns.target == Target::Solana {
if res.accounts.is_absent()
&& !matches!(
ns.functions[context.function_no.unwrap()].visibility,
Visibility::External(_)
)
&& !ns.functions[context.function_no.unwrap()].is_constructor()
{
diagnostics.push(Diagnostic::error(
*loc,
"accounts are required for calling a contract. You can either provide the \
accounts with the {accounts: ...} call argument or change this function's \
visibility to external"
.to_string(),
));
return Err(());
}
if let Some(callee_contract_no) = callee_contract {
if res.program_id.is_none() && ns.contracts[callee_contract_no].program_id.is_none() {
diagnostics.push(Diagnostic::error(
*loc,
"a contract needs a program id to be called. Either a '@program_id' \
must be declared above a contract or the {program_id: ...} call argument \
must be present"
.to_string(),
));
return Err(());
}
}
}
Ok(res)
}
pub fn named_call_expr(
loc: &pt::Loc,
ty: &pt::Expression,
args: &[pt::NamedArgument],
is_destructible: bool,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
let mut nullsink = Diagnostics::default();
match ns.resolve_type(
context.file_no,
context.contract_no,
ResolveTypeContext::Casting,
ty,
&mut nullsink,
) {
Ok(Type::Struct(str_ty)) => {
let id = ns.expr_to_identifier_path(ty).unwrap();
return named_struct_literal(
loc,
id,
&str_ty,
args,
context,
ns,
symtable,
diagnostics,
);
}
Ok(_) => {
diagnostics.push(Diagnostic::error(
*loc,
"struct or function expected".to_string(),
));
return Err(());
}
_ => {}
}
if context.constant {
diagnostics.push(Diagnostic::error(
*loc,
"cannot call function in constant expression".to_string(),
));
return Err(());
}
let expr = named_function_call_expr(
loc,
ty,
args,
context,
ns,
symtable,
diagnostics,
resolve_to,
)?;
check_function_call(ns, &expr, symtable);
if expr.tys().len() > 1 && !is_destructible {
diagnostics.push(Diagnostic::error(
*loc,
"destucturing statement needed for function that returns multiple values".to_string(),
));
return Err(());
}
Ok(expr)
}
pub fn call_expr(
loc: &pt::Loc,
ty: &pt::Expression,
args: &[pt::Expression],
is_destructible: bool,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
let mut nullsink = Diagnostics::default();
let ty = ty.remove_parenthesis();
match ns.resolve_type(
context.file_no,
context.contract_no,
ResolveTypeContext::Casting,
ty,
&mut nullsink,
) {
Ok(Type::Struct(str_ty)) => {
let id = ns.expr_to_identifier_path(ty).unwrap();
return struct_literal(loc, id, &str_ty, args, context, ns, symtable, diagnostics);
}
Ok(to) => {
return if args.is_empty() {
diagnostics.push(Diagnostic::error(
*loc,
"missing argument to cast".to_string(),
));
Err(())
} else if args.len() > 1 {
diagnostics.push(Diagnostic::error(
*loc,
"too many arguments to cast".to_string(),
));
Err(())
} else {
let expr = expression(
&args[0],
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
)?;
expr.cast(loc, &to, false, ns, diagnostics)
};
}
Err(_) => (),
}
let expr = match ty.remove_parenthesis() {
pt::Expression::New(_, ty) => new(loc, ty, args, context, ns, symtable, diagnostics)?,
pt::Expression::FunctionCallBlock(loc, expr, _)
if matches!(expr.remove_parenthesis(), pt::Expression::New(..)) =>
{
new(loc, ty, args, context, ns, symtable, diagnostics)?
}
_ => {
deprecated_constructor_arguments(ty, diagnostics)?;
function_call_expr(
loc,
ty,
args,
context,
ns,
symtable,
diagnostics,
resolve_to,
)?
}
};
check_function_call(ns, &expr, symtable);
if expr.tys().len() > 1 && !is_destructible {
diagnostics.push(Diagnostic::error(
*loc,
"destucturing statement needed for function that returns multiple values".to_string(),
));
return Err(());
}
Ok(expr)
}
pub fn function_call_expr(
loc: &pt::Loc,
ty: &pt::Expression,
args: &[pt::Expression],
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
match ty.remove_parenthesis() {
pt::Expression::MemberAccess(_, member, func) => method_call_pos_args(
loc,
member,
func,
args,
&call_args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
resolve_to,
),
pt::Expression::Variable(id) => {
if builtin::is_builtin_call(None, &id.name, ns) {
return {
let expr = builtin::resolve_call(
&id.loc,
None,
&id.name,
args,
context,
ns,
symtable,
diagnostics,
)?;
if expr.tys().len() > 1 {
diagnostics.push(Diagnostic::error(
*loc,
format!("builtin function '{}' returns more than one value", id.name),
));
Err(())
} else {
Ok(expr)
}
};
}
if symtable.find(context, &id.name).is_some()
|| matches!(
ns.resolve_var(context.file_no, context.contract_no, id, true),
Some(Symbol::Variable(..))
)
{
call_function_type(
loc,
ty,
args,
&call_args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
resolve_to,
)
} else {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not permitted for internal calls".to_string(),
));
return Err(());
}
let id_path = pt::IdentifierPath {
loc: id.loc,
identifiers: vec![id.clone()],
};
function_call_pos_args(
loc,
&id_path,
pt::FunctionTy::Function,
args,
available_functions(&id.name, true, context.file_no, context.contract_no, ns),
true,
context,
ns,
resolve_to,
symtable,
diagnostics,
)
}
}
_ => call_function_type(
loc,
ty,
args,
&call_args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
resolve_to,
),
}
}
pub fn named_function_call_expr(
loc: &pt::Loc,
ty: &pt::Expression,
args: &[pt::NamedArgument],
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
match ty {
pt::Expression::MemberAccess(_, member, func) => method_call_named_args(
loc,
member,
func,
args,
&call_args,
call_args_loc,
context,
ns,
symtable,
diagnostics,
resolve_to,
),
pt::Expression::Variable(id) => {
if let Some(loc) = call_args_loc {
diagnostics.push(Diagnostic::error(
loc,
"call arguments not permitted for internal calls".to_string(),
));
return Err(());
}
let id_path = pt::IdentifierPath {
loc: id.loc,
identifiers: vec![id.clone()],
};
function_call_named_args(
loc,
&id_path,
args,
available_functions(&id.name, true, context.file_no, context.contract_no, ns),
true,
context,
resolve_to,
ns,
symtable,
diagnostics,
)
}
pt::Expression::ArraySubscript(..) => {
diagnostics.push(Diagnostic::error(
ty.loc(),
"unexpected array type".to_string(),
));
Err(())
}
_ => {
diagnostics.push(Diagnostic::error(
ty.loc(),
"expression not expected here".to_string(),
));
Err(())
}
}
}
pub(crate) fn function_returns(ftype: &Function, resolve_to: ResolveTo) -> Vec<Type> {
if !ftype.returns.is_empty() && !matches!(resolve_to, ResolveTo::Discard) {
ftype.returns.iter().map(|p| p.ty.clone()).collect()
} else {
vec![Type::Void]
}
}
pub(crate) fn function_type(func: &Function, external: bool, resolve_to: ResolveTo) -> Type {
let params = func.params.iter().map(|p| p.ty.clone()).collect();
let mutability = func.mutability.clone();
let returns = function_returns(func, resolve_to);
if external {
Type::ExternalFunction {
params,
mutability,
returns,
}
} else {
Type::InternalFunction {
params,
mutability,
returns,
}
}
}
pub(crate) fn evaluate_argument(
arg: &pt::Expression,
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
arg_ty: &Type,
errors: &mut Diagnostics,
cast_args: &mut Vec<Expression>,
) -> bool {
expression(arg, context, ns, symtable, errors, ResolveTo::Type(arg_ty))
.and_then(|arg| arg.cast(&arg.loc(), arg_ty, true, ns, errors))
.map(|expr| cast_args.push(expr))
.is_ok()
}
fn resolve_internal_call(
loc: &Loc,
id: &pt::IdentifierPath,
function_no: usize,
context: &mut ExprContext,
resolve_to: ResolveTo,
virtual_call: bool,
cast_args: Vec<Expression>,
ns: &Namespace,
errors: &mut Diagnostics,
) -> Option<Expression> {
let func = &ns.functions[function_no];
if func.contract_no != context.contract_no && func.is_private() {
errors.push(Diagnostic::error_with_note(
*loc,
format!("cannot call private {}", func.ty),
func.loc_prototype,
format!("declaration of {} '{}'", func.ty, func.id),
));
return None;
} else if let (Some(base_no), Some(derived_no)) = (func.contract_no, context.contract_no) {
if is_base(base_no, derived_no, ns) && matches!(func.visibility, Visibility::External(_)) {
if func.is_accessor {
errors.push(Diagnostic::error_with_note(
*loc,
"accessor function cannot be called via an internal function call".to_string(),
func.loc_prototype,
format!("declaration of '{}'", func.id),
));
} else {
errors.push(Diagnostic::error_with_note(
*loc,
"functions declared external cannot be called via an internal function call"
.to_string(),
func.loc_prototype,
format!("declaration of {} '{}'", func.ty, func.id),
));
}
return None;
}
}
let returns = function_returns(func, resolve_to);
let ty = function_type(func, false, resolve_to);
Some(Expression::InternalFunctionCall {
loc: *loc,
returns,
function: Box::new(Expression::InternalFunction {
loc: *loc,
id: id.clone(),
ty,
function_no,
signature: if virtual_call && (func.is_virtual || func.is_override.is_some()) {
Some(func.signature.clone())
} else {
None
},
}),
args: cast_args,
})
}
fn contract_call_named_args(
loc: &pt::Loc,
var_expr: Option<Expression>,
func_name: &pt::Identifier,
args: &[pt::NamedArgument],
call_args: &[&pt::NamedArgument],
external_contract_no: usize,
context: &mut ExprContext,
symtable: &mut Symtable,
ns: &mut Namespace,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
let mut arguments = HashMap::new();
if args.iter().fold(false, |mut acc, arg| {
if arguments.contains_key(arg.name.name.as_str()) {
diagnostics.push(Diagnostic::error(
arg.name.loc,
format!("duplicate argument with name '{}'", arg.name.name),
));
let _ = expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
);
} else {
acc |= expression(
&arg.expr,
context,
ns,
symtable,
diagnostics,
ResolveTo::Unknown,
)
.is_err()
}
arguments.insert(arg.name.name.as_str(), &arg.expr);
acc
}) {
return Err(());
}
let (call_args, name_matches) = match preprocess_contract_call(
loc,
call_args,
external_contract_no,
func_name,
args,
context,
ns,
symtable,
diagnostics,
) {
PreProcessedCall::Success {
call_args,
name_matches,
} => (call_args, name_matches),
PreProcessedCall::DefaultConstructor(expr) => return Ok(expr),
PreProcessedCall::Error => return Err(()),
};
let mut call_diagnostics = Diagnostics::default();
let mut resolved_calls = Vec::new();
for function_no in &name_matches {
let func = &ns.functions[*function_no];
let mut candidate_diagnostics = Diagnostics::default();
let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
let params_len = func.params.len();
let mut cast_args = Vec::new();
if unnamed_params > 0 {
candidate_diagnostics.push(Diagnostic::cast_error_with_note(
*loc,
format!(
"function cannot be called with named arguments as {unnamed_params} of its parameters do not have names"
),
func.loc_prototype,
format!("definition of {}", func.id),
));
} else if params_len != args.len() {
candidate_diagnostics.push(Diagnostic::cast_error(
*loc,
format!(
"function expects {} arguments, {} provided",
params_len,
args.len()
),
));
}
for i in 0..params_len {
let param = ns.functions[*function_no].params[i].clone();
if param.id.is_none() {
continue;
}
let arg = match arguments.get(param.name_as_str()) {
Some(a) => a,
None => {
candidate_diagnostics.push(Diagnostic::cast_error(
*loc,
format!(
"missing argument '{}' to function '{}'",
param.name_as_str(),
func_name.name,
),
));
continue;
}
};
let ty = param.ty.clone();
evaluate_argument(
arg,
context,
ns,
symtable,
&ty,
&mut candidate_diagnostics,
&mut cast_args,
);
}
if candidate_diagnostics.any_errors() {
if name_matches.len() != 1 {
candidate_diagnostics.push(Diagnostic::error(
*loc,
"cannot find overloaded function which matches signature".into(),
));
let func = &ns.functions[*function_no];
candidate_diagnostics.iter_mut().for_each(|diagnostic| {
diagnostic.notes.push(Note {
loc: func.loc,
message: "candidate function".into(),
})
});
}
} else if let Ok(resolved_call) = contract_call_match(
loc,
func_name,
*function_no,
external_contract_no,
call_args.clone(),
cast_args,
var_expr.as_ref(),
ns,
&mut candidate_diagnostics,
resolve_to,
) {
resolved_calls.push((*function_no, resolved_call));
continue;
}
call_diagnostics.extend(candidate_diagnostics);
}
match resolved_calls.len() {
0 => {
diagnostics.extend(call_diagnostics);
if name_matches.is_empty() {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"contract '{}' does not have function '{}'",
ns.contracts[external_contract_no].id, func_name.name
),
));
}
Err(())
}
1 => Ok(resolved_calls[0].1.clone()),
_ => {
diagnostics.push(Diagnostic::error_with_notes(
*loc,
"function call can be resolved to multiple functions".into(),
resolved_calls
.iter()
.map(|(func_no, _)| {
let func = &ns.functions[*func_no];
Note {
loc: func.loc,
message: "candidate function".into(),
}
})
.collect(),
));
Err(())
}
}
}
fn contract_call_pos_args(
loc: &pt::Loc,
external_contract_no: usize,
func: &pt::Identifier,
var_expr: Option<&Expression>,
args: &[pt::Expression],
call_args: &[&pt::NamedArgument],
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Option<Expression>, ()> {
let (call_args, name_matches) = match preprocess_contract_call(
loc,
call_args,
external_contract_no,
func,
args,
context,
ns,
symtable,
diagnostics,
) {
PreProcessedCall::Success {
call_args,
name_matches,
} => (call_args, name_matches),
PreProcessedCall::DefaultConstructor(expr) => return Ok(Some(expr)),
PreProcessedCall::Error => return Err(()),
};
let mut call_diagnostics = Diagnostics::default();
let mut resolved_calls = Vec::new();
if args.iter().fold(false, |acc, arg| {
acc | expression(arg, context, ns, symtable, diagnostics, ResolveTo::Unknown).is_err()
}) {
return Err(());
}
for function_no in &name_matches {
let mut candidate_diagnostics = Diagnostics::default();
let mut cast_args = Vec::new();
let params_len = ns.functions[*function_no].params.len();
if params_len != args.len() {
candidate_diagnostics.push(Diagnostic::error(
*loc,
format!(
"function expects {} arguments, {} provided",
params_len,
args.len()
),
));
} else {
for (i, arg) in args.iter().enumerate() {
let ty = ns.functions[*function_no].params[i].ty.clone();
evaluate_argument(
arg,
context,
ns,
symtable,
&ty,
&mut candidate_diagnostics,
&mut cast_args,
);
}
}
if candidate_diagnostics.any_errors() {
if name_matches.len() != 1 {
candidate_diagnostics.iter_mut().for_each(|diagnostic| {
diagnostic.notes.push(Note {
loc: ns.functions[*function_no].loc_prototype,
message: "candidate function".into(),
})
});
candidate_diagnostics.push(Diagnostic::error(
*loc,
"cannot find overloaded function which matches signature".into(),
));
}
} else if let Ok(resolved_call) = contract_call_match(
loc,
func,
*function_no,
external_contract_no,
call_args.clone(),
cast_args,
var_expr,
ns,
&mut candidate_diagnostics,
resolve_to,
) {
resolved_calls.push((*function_no, resolved_call));
continue;
}
call_diagnostics.extend(candidate_diagnostics);
}
match resolved_calls.len() {
0 => {
if let Some(var) = var_expr {
match using::try_resolve_using_call(
loc,
func,
var,
context,
args,
symtable,
diagnostics,
ns,
resolve_to,
) {
Ok(Some(expr)) => {
return Ok(Some(expr));
}
Ok(None) => (),
Err(_) => {
return Err(());
}
}
}
diagnostics.extend(call_diagnostics);
if name_matches.is_empty() {
diagnostics.push(Diagnostic::error(
func.loc,
format!("unknown function '{}'", func.name),
));
}
Err(())
}
1 => Ok(Some(resolved_calls[0].1.clone())),
_ => {
diagnostics.extend(call_diagnostics);
diagnostics.push(Diagnostic::error_with_notes(
*loc,
"function call can be resolved to multiple functions".into(),
resolved_calls
.iter()
.map(|(func_no, _)| {
let func = &ns.functions[*func_no];
Note {
loc: func.loc,
message: "candidate function".into(),
}
})
.collect(),
));
Err(())
}
}
}
fn is_solana_external_call(
list: &[(pt::Loc, usize)],
contract_no: Option<usize>,
call_args_loc: &Option<pt::Loc>,
ns: &Namespace,
) -> Option<usize> {
if ns.target == Target::Solana
&& list.len() == 1
&& ns.functions[list[0].1].contract_no != contract_no
{
if let (Some(callee), Some(caller)) = (ns.functions[list[0].1].contract_no, contract_no) {
if is_base(callee, caller, ns) && call_args_loc.is_none() {
return None;
}
}
return ns.functions[list[0].1].contract_no;
}
None
}
enum PreProcessedCall {
Success {
call_args: CallArgs,
name_matches: Vec<usize>,
},
DefaultConstructor(Expression),
Error,
}
fn preprocess_contract_call<T>(
loc: &pt::Loc,
call_args: &[&pt::NamedArgument],
external_contract_no: usize,
func: &pt::Identifier,
args: &[T],
context: &mut ExprContext,
ns: &mut Namespace,
symtable: &mut Symtable,
diagnostics: &mut Diagnostics,
) -> PreProcessedCall {
let call_args = if let Ok(call_args) = parse_call_args(
loc,
call_args,
Some(external_contract_no),
func.name != "new",
context,
ns,
symtable,
diagnostics,
) {
call_args
} else {
return PreProcessedCall::Error;
};
let mut name_matches: Vec<usize> = Vec::new();
for function_no in ns.contracts[external_contract_no].all_functions.keys() {
if func.name != ns.functions[*function_no].id.name
|| ns.functions[*function_no].ty != pt::FunctionTy::Function
{
continue;
}
name_matches.push(*function_no);
}
if ns.target == Target::Solana && func.name == "new" {
solana_constructor_check(
loc,
external_contract_no,
diagnostics,
context,
&call_args,
ns,
);
let constructor_nos = ns.contracts[external_contract_no].constructors(ns);
if !constructor_nos.is_empty() {
assert_eq!(constructor_nos.len(), 1);
name_matches.push(constructor_nos[0]);
} else if !args.is_empty() {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"'{}' constructor takes no argument",
ns.contracts[external_contract_no].id
),
));
return PreProcessedCall::Error;
} else {
return PreProcessedCall::DefaultConstructor(Expression::Constructor {
loc: *loc,
contract_no: external_contract_no,
constructor_no: None,
args: vec![],
call_args,
});
}
}
PreProcessedCall::Success {
call_args,
name_matches,
}
}
fn contract_call_match(
loc: &pt::Loc,
func: &pt::Identifier,
function_no: usize,
external_contract_no: usize,
call_args: CallArgs,
cast_args: Vec<Expression>,
var_expr: Option<&Expression>,
ns: &Namespace,
diagnostics: &mut Diagnostics,
resolve_to: ResolveTo,
) -> Result<Expression, ()> {
if !ns.functions[function_no].is_public() {
diagnostics.push(Diagnostic::error(
*loc,
format!("function '{}' is not 'public' or 'external'", func.name),
));
return Err(());
} else if let Some(value) = &call_args.value {
if !value.const_zero(ns) && !ns.functions[function_no].is_payable() {
diagnostics.push(Diagnostic::error(
*loc,
format!(
"sending value to function '{}' which is not payable",
func.name
),
));
return Err(());
}
}
let func = &ns.functions[function_no];
let returns = function_returns(func, resolve_to);
let ty = function_type(func, true, resolve_to);
let (address, implicit) = if let Some(program_id_var) = &call_args.program_id {
(*program_id_var.clone(), false)
} else if let Some(address_id) = &ns.contracts[external_contract_no].program_id {
(
Expression::NumberLiteral {
loc: *loc,
ty: Type::Address(false),
value: BigInt::from_bytes_be(Sign::Plus, address_id),
},
false,
)
} else if let Some(var) = var_expr {
(var.clone(), true)
} else {
unreachable!("address not found")
};
Ok({
Expression::ExternalFunctionCall {
loc: *loc,
returns,
function: Box::new(Expression::ExternalFunction {
loc: *loc,
ty,
function_no,
address: Box::new(address.cast(
&address.loc(),
&Type::Contract(func.contract_no.unwrap()),
implicit,
ns,
diagnostics,
)?),
}),
args: cast_args,
call_args,
}
})
}