瀏覽代碼

Refactor `sema/expression.rs` (#1137)

Signed-off-by: Lucas Steuernagel <lucas.tnagel@gmail.com>
Lucas Steuernagel 2 年之前
父節點
當前提交
f8586a449c

+ 2 - 1
src/codegen/expression.rs

@@ -22,7 +22,8 @@ use crate::sema::{
     },
     diagnostics::Diagnostics,
     eval::{eval_const_number, eval_const_rational},
-    expression::{bigint_to_expression, ResolveTo},
+    expression::integers::bigint_to_expression,
+    expression::ResolveTo,
 };
 use crate::Target;
 use num_bigint::BigInt;

+ 1 - 1
src/codegen/yul/builtin.rs

@@ -10,7 +10,7 @@ use crate::codegen::{
 use crate::sema::ast::{Namespace, RetrieveType, Type};
 use crate::sema::{
     diagnostics::Diagnostics,
-    expression::coerce_number,
+    expression::integers::coerce_number,
     yul::{ast, builtin::YulBuiltInFunction},
 };
 use num_bigint::BigInt;

+ 2 - 1
src/sema/builtin.rs

@@ -6,9 +6,10 @@ use super::ast::{
 };
 use super::diagnostics::Diagnostics;
 use super::eval::eval_const_number;
-use super::expression::{expression, ExprContext, ResolveTo};
+use super::expression::{ExprContext, ResolveTo};
 use super::symtable::Symtable;
 use crate::sema::ast::{RetrieveType, Tag, UserTypeDecl};
+use crate::sema::expression::resolve_expression::expression;
 use crate::Target;
 use num_bigint::BigInt;
 use num_traits::One;

+ 2 - 1
src/sema/contracts.rs

@@ -3,11 +3,12 @@
 use super::{
     annotions_not_allowed, ast,
     diagnostics::Diagnostics,
-    expression::{compatible_mutability, match_constructor_to_args, ExprContext},
+    expression::{compatible_mutability, ExprContext},
     functions, statements,
     symtable::Symtable,
     using, variables, ContractDefinition,
 };
+use crate::sema::expression::constructor::match_constructor_to_args;
 use crate::{sema::ast::Namespace, sema::unused_variable::emit_warning_local_variable};
 use num_bigint::BigInt;
 use num_traits::Zero;

+ 0 - 8391
src/sema/expression.rs

@@ -1,8391 +0,0 @@
-// SPDX-License-Identifier: Apache-2.0
-
-use super::address::to_hexstr_eip55;
-use super::ast::{
-    ArrayLength, Builtin, CallArgs, CallTy, Diagnostic, Expression, Function, Mutability,
-    Namespace, RetrieveType, StringLocation, StructType, Symbol, Type,
-};
-use super::builtin;
-use super::contracts::is_base;
-use super::diagnostics::Diagnostics;
-use super::eval::check_term_for_constant_overflow;
-use super::eval::eval_const_number;
-use super::eval::eval_const_rational;
-use super::format::string_format;
-use super::{symtable::Symtable, using};
-use crate::sema::unused_variable::{
-    assigned_variable, check_function_call, check_var_usage_expression, used_variable,
-};
-use crate::sema::Recurse;
-use crate::Target;
-use base58::{FromBase58, FromBase58Error};
-use num_bigint::{BigInt, Sign};
-use num_rational::BigRational;
-use num_traits::{FromPrimitive, Num, One, Pow, ToPrimitive, Zero};
-use solang_parser::pt::{self, CodeLocation, Loc};
-use std::{
-    cmp::Ordering,
-    collections::{BTreeMap, HashMap},
-    ops::{Mul, Shl, Sub},
-    str::FromStr,
-};
-
-impl RetrieveType for Expression {
-    fn ty(&self) -> Type {
-        match self {
-            Expression::BoolLiteral { .. }
-            | Expression::More { .. }
-            | Expression::Less { .. }
-            | Expression::MoreEqual { .. }
-            | Expression::LessEqual { .. }
-            | Expression::Equal { .. }
-            | Expression::Or { .. }
-            | Expression::And { .. }
-            | Expression::NotEqual { .. }
-            | Expression::Not { .. }
-            | Expression::StringCompare { .. } => Type::Bool,
-            Expression::CodeLiteral { .. } => Type::DynamicBytes,
-            Expression::StringConcat { ty, .. }
-            | Expression::BytesLiteral { ty, .. }
-            | Expression::NumberLiteral { ty, .. }
-            | Expression::RationalNumberLiteral { ty, .. }
-            | Expression::StructLiteral { ty, .. }
-            | Expression::ArrayLiteral { ty, .. }
-            | Expression::ConstArrayLiteral { ty, .. }
-            | Expression::Add { ty, .. }
-            | Expression::Subtract { ty, .. }
-            | Expression::Multiply { ty, .. }
-            | Expression::Divide { ty, .. }
-            | Expression::Modulo { ty, .. }
-            | Expression::Power { ty, .. }
-            | Expression::BitwiseOr { ty, .. }
-            | Expression::BitwiseAnd { ty, .. }
-            | Expression::BitwiseXor { ty, .. }
-            | Expression::ShiftLeft { ty, .. }
-            | Expression::ShiftRight { ty, .. }
-            | Expression::Variable { ty, .. }
-            | Expression::ConstantVariable { ty, .. }
-            | Expression::StorageVariable { ty, .. }
-            | Expression::Load { ty, .. }
-            | Expression::GetRef { ty, .. }
-            | Expression::StorageLoad { ty, .. }
-            | Expression::Complement { ty, .. }
-            | Expression::UnaryMinus { ty, .. }
-            | Expression::ConditionalOperator { ty, .. }
-            | Expression::StructMember { ty, .. }
-            | Expression::AllocDynamicBytes { ty, .. }
-            | Expression::PreIncrement { ty, .. }
-            | Expression::PreDecrement { ty, .. }
-            | Expression::PostIncrement { ty, .. }
-            | Expression::PostDecrement { ty, .. }
-            | Expression::Assign { ty, .. } => ty.clone(),
-            Expression::Subscript { ty, .. } => ty.clone(),
-            Expression::ZeroExt { to, .. }
-            | Expression::SignExt { to, .. }
-            | Expression::Trunc { to, .. }
-            | Expression::CheckingTrunc { to, .. }
-            | Expression::Cast { to, .. }
-            | Expression::BytesCast { to, .. } => to.clone(),
-            Expression::StorageArrayLength { ty, .. } => ty.clone(),
-            Expression::ExternalFunctionCallRaw { .. } => {
-                panic!("two return values");
-            }
-            Expression::Builtin { tys: returns, .. }
-            | Expression::InternalFunctionCall { returns, .. }
-            | Expression::ExternalFunctionCall { returns, .. } => {
-                assert_eq!(returns.len(), 1);
-                returns[0].clone()
-            }
-            Expression::List { list, .. } => {
-                assert_eq!(list.len(), 1);
-
-                list[0].ty()
-            }
-            Expression::Constructor { contract_no, .. } => Type::Contract(*contract_no),
-            Expression::InterfaceId { .. } => Type::FunctionSelector,
-            Expression::FormatString { .. } => Type::String,
-            // codegen Expressions
-            Expression::InternalFunction { ty, .. } => ty.clone(),
-            Expression::ExternalFunction { ty, .. } => ty.clone(),
-        }
-    }
-}
-
-impl Expression {
-    /// Is this expression 0
-    fn const_zero(&self, ns: &Namespace) -> bool {
-        if let Ok((_, value)) = eval_const_number(self, ns) {
-            value == BigInt::zero()
-        } else {
-            false
-        }
-    }
-
-    /// Return the type for this expression.
-    pub fn tys(&self) -> Vec<Type> {
-        match self {
-            Expression::Builtin { tys: returns, .. }
-            | Expression::InternalFunctionCall { returns, .. }
-            | Expression::ExternalFunctionCall { returns, .. } => returns.to_vec(),
-            Expression::List { list, .. } => list.iter().map(|e| e.ty()).collect(),
-            Expression::ExternalFunctionCallRaw { .. } => vec![Type::Bool, Type::DynamicBytes],
-            _ => vec![self.ty()],
-        }
-    }
-
-    /// Cast from one type to another, which also automatically derefs any Type::Ref() type.
-    /// if the cast is explicit (e.g. bytes32(bar) then implicit should be set to false.
-    pub(super) fn cast(
-        &self,
-        loc: &pt::Loc,
-        to: &Type,
-        implicit: bool,
-        ns: &Namespace,
-        diagnostics: &mut Diagnostics,
-    ) -> Result<Expression, ()> {
-        let from = self.ty();
-        if &from == to {
-            return Ok(self.clone());
-        }
-
-        if from == Type::Unresolved || *to == Type::Unresolved {
-            return Ok(self.clone());
-        }
-
-        // First of all, if we have a ref then derefence it
-        if let Type::Ref(r) = &from {
-            return if r.is_fixed_reference_type() {
-                // A struct/fixed array *value* is simply the type, e.g. Type::Struct(_)
-                // An assignable struct value, e.g. member of another struct, is Type::Ref(Type:Struct(_)).
-                // However, the underlying types are identical: simply a pointer.
-                //
-                // So a Type::Ref(Type::Struct(_)) can be cast to Type::Struct(_).
-                //
-                // The Type::Ref(..) just means it can be used as an l-value and assigned
-                // a new value, unlike say, a struct literal.
-                if r.as_ref() == to {
-                    Ok(self.clone())
-                } else {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "conversion from {} to {} not possible",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                }
-            } else {
-                Expression::Load {
-                    loc: *loc,
-                    ty: r.as_ref().clone(),
-                    expr: Box::new(self.clone()),
-                }
-                .cast(loc, to, implicit, ns, diagnostics)
-            };
-        }
-
-        // If it's a storage reference then load the value. The expr is the storage slot
-        if let Type::StorageRef(_, r) = from {
-            if let Expression::Subscript { array_ty: ty, .. } = self {
-                if ty.is_storage_bytes() {
-                    return Ok(self.clone());
-                }
-            }
-
-            return Expression::StorageLoad {
-                loc: *loc,
-                ty: *r,
-                expr: Box::new(self.clone()),
-            }
-            .cast(loc, to, implicit, ns, diagnostics);
-        }
-
-        // Special case: when converting literal sign can change if it fits
-        match (self, &from, to) {
-            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Uint(to_len))
-                if p.is_primitive() =>
-            {
-                return if value.sign() == Sign::Minus {
-                    if implicit {
-                        diagnostics.push(Diagnostic::cast_error(
-                            *loc,
-                            format!(
-                                "implicit conversion cannot change negative number to '{}'",
-                                to.to_string(ns)
-                            ),
-                        ));
-                        Err(())
-                    } else {
-                        // Convert to little endian so most significant bytes are at the end; that way
-                        // we can simply resize the vector to the right size
-                        let mut bs = value.to_signed_bytes_le();
-
-                        bs.resize(to_len as usize / 8, 0xff);
-                        Ok(Expression::NumberLiteral {
-                            loc: *loc,
-                            ty: Type::Uint(to_len),
-                            value: BigInt::from_bytes_le(Sign::Plus, &bs),
-                        })
-                    }
-                } else if value.bits() >= to_len as u64 {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would truncate from '{}' to '{}'",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::NumberLiteral {
-                        loc: *loc,
-                        ty: Type::Uint(to_len),
-                        value: value.clone(),
-                    })
-                };
-            }
-            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Int(to_len))
-                if p.is_primitive() =>
-            {
-                return if value.bits() >= to_len as u64 {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would truncate from '{}' to '{}'",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::NumberLiteral {
-                        loc: *loc,
-                        ty: Type::Int(to_len),
-                        value: value.clone(),
-                    })
-                };
-            }
-            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Bytes(to_len))
-                if p.is_primitive() =>
-            {
-                // round up the number of bits to bytes
-                let bytes = (value.bits() + 7) / 8;
-                return if value.sign() == Sign::Minus {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "negative number cannot be converted to type '{}'",
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if value.sign() == Sign::Plus && bytes != to_len as u64 {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "number of {} bytes cannot be converted to type '{}'",
-                            bytes,
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::NumberLiteral {
-                        loc: *loc,
-                        ty: Type::Bytes(to_len),
-                        value: value.clone(),
-                    })
-                };
-            }
-            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Address(payable))
-                if p.is_primitive() =>
-            {
-                // note: negative values are allowed
-                return if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        String::from("implicit conversion from int to address not allowed"),
-                    ));
-                    Err(())
-                } else if value.bits() > ns.address_length as u64 * 8 {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "number larger than possible in {} byte address",
-                            ns.address_length,
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::NumberLiteral {
-                        loc: *loc,
-                        ty: Type::Address(payable),
-                        value: value.clone(),
-                    })
-                };
-            }
-            // Literal strings can be implicitly lengthened
-            (&Expression::BytesLiteral { ref value, .. }, p, &Type::Bytes(to_len))
-                if p.is_primitive() =>
-            {
-                return if value.len() > to_len as usize && implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would truncate from '{}' to '{}'",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    let mut bs = value.to_owned();
-
-                    // Add zero's at the end as needed
-                    bs.resize(to_len as usize, 0);
-
-                    Ok(Expression::BytesLiteral {
-                        loc: *loc,
-                        ty: Type::Bytes(to_len),
-                        value: bs,
-                    })
-                };
-            }
-            (&Expression::BytesLiteral { loc, ref value, .. }, _, &Type::DynamicBytes)
-            | (&Expression::BytesLiteral { loc, ref value, .. }, _, &Type::String) => {
-                return Ok(Expression::AllocDynamicBytes {
-                    loc,
-                    ty: to.clone(),
-                    length: Box::new(Expression::NumberLiteral {
-                        loc,
-                        ty: Type::Uint(32),
-                        value: BigInt::from(value.len()),
-                    }),
-                    init: Some(value.clone()),
-                });
-            }
-            (&Expression::NumberLiteral { ref value, .. }, _, &Type::Rational) => {
-                return Ok(Expression::RationalNumberLiteral {
-                    loc: *loc,
-                    ty: Type::Rational,
-                    value: BigRational::from(value.clone()),
-                });
-            }
-
-            (
-                &Expression::ArrayLiteral { .. },
-                Type::Array(from_ty, from_dims),
-                Type::Array(to_ty, to_dims),
-            ) => {
-                if from_ty == to_ty
-                    && from_dims.len() == to_dims.len()
-                    && from_dims.len() == 1
-                    && matches!(to_dims.last().unwrap(), ArrayLength::Dynamic)
-                {
-                    return Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    });
-                }
-            }
-
-            _ => (),
-        };
-
-        self.cast_types(loc, &from, to, implicit, ns, diagnostics)
-    }
-
-    fn cast_types(
-        &self,
-        loc: &pt::Loc,
-        from: &Type,
-        to: &Type,
-        implicit: bool,
-        ns: &Namespace,
-        diagnostics: &mut Diagnostics,
-    ) -> Result<Expression, ()> {
-        let address_bits = ns.address_length as u16 * 8;
-
-        match (&from, &to) {
-            // Solana builtin AccountMeta struct wants a pointer to an address for the pubkey field,
-            // not an address. For this specific field we have a special Expression::GetRef() which
-            // gets the pointer to an address
-            (Type::Address(_), Type::Ref(to)) if matches!(to.as_ref(), Type::Address(..)) => {
-                Ok(Expression::GetRef {
-                    loc: *loc,
-                    ty: Type::Ref(Box::new(from.clone())),
-                    expr: Box::new(self.clone()),
-                })
-            }
-            (Type::Uint(from_width), Type::Enum(enum_no))
-            | (Type::Int(from_width), Type::Enum(enum_no)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion from {} to {} not allowed",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    return Err(());
-                }
-
-                let enum_ty = &ns.enums[*enum_no];
-
-                // TODO would be help to have current contract to resolve contract constants
-                if let Ok((_, big_number)) = eval_const_number(self, ns) {
-                    if let Some(number) = big_number.to_usize() {
-                        if enum_ty.values.len() > number {
-                            return Ok(Expression::NumberLiteral {
-                                loc: self.loc(),
-                                ty: to.clone(),
-                                value: big_number,
-                            });
-                        }
-                    }
-
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "enum {} has no value with ordinal {}",
-                            to.to_string(ns),
-                            big_number
-                        ),
-                    ));
-                    return Err(());
-                }
-
-                let to_width = enum_ty.ty.bits(ns);
-
-                // TODO needs runtime checks
-                match from_width.cmp(&to_width) {
-                    Ordering::Greater => Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Less => Ok(Expression::ZeroExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Equal => Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                }
-            }
-            (Type::Enum(enum_no), Type::Uint(to_width))
-            | (Type::Enum(enum_no), Type::Int(to_width)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion from {} to {} not allowed",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    return Err(());
-                }
-                let enum_ty = &ns.enums[*enum_no];
-                let from_width = enum_ty.ty.bits(ns);
-
-                match from_width.cmp(to_width) {
-                    Ordering::Greater => Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Less => Ok(Expression::ZeroExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Equal => Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                }
-            }
-            (Type::Bytes(n), Type::FunctionSelector) if *n == ns.target.selector_length() => {
-                Ok(Expression::Cast {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                })
-            }
-
-            (Type::Bytes(1), Type::Uint(8)) | (Type::Uint(8), Type::Bytes(1)) => Ok(self.clone()),
-            (Type::Uint(from_len), Type::Uint(to_len)) => match from_len.cmp(to_len) {
-                Ordering::Greater => {
-                    if implicit {
-                        diagnostics.push(Diagnostic::cast_error(
-                            *loc,
-                            format!(
-                                "implicit conversion would truncate from {} to {}",
-                                from.to_string(ns),
-                                to.to_string(ns)
-                            ),
-                        ));
-                        Err(())
-                    } else {
-                        Ok(Expression::Trunc {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(self.clone()),
-                        })
-                    }
-                }
-                Ordering::Less => Ok(Expression::ZeroExt {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                }),
-                Ordering::Equal => Ok(Expression::Cast {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                }),
-            },
-            (Type::Int(from_len), Type::Int(to_len)) => match from_len.cmp(to_len) {
-                Ordering::Greater => {
-                    if implicit {
-                        diagnostics.push(Diagnostic::cast_error(
-                            *loc,
-                            format!(
-                                "implicit conversion would truncate from {} to {}",
-                                from.to_string(ns),
-                                to.to_string(ns)
-                            ),
-                        ));
-                        Err(())
-                    } else {
-                        Ok(Expression::Trunc {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(self.clone()),
-                        })
-                    }
-                }
-                Ordering::Less => Ok(Expression::SignExt {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                }),
-                Ordering::Equal => Ok(Expression::Cast {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                }),
-            },
-            (Type::Uint(from_len), Type::Int(to_len)) if to_len > from_len => {
-                Ok(Expression::ZeroExt {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                })
-            }
-            (Type::Int(from_len), Type::Uint(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would change sign from {} to {}",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if from_len > to_len {
-                    Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else if from_len < to_len {
-                    Ok(Expression::SignExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            (Type::Uint(from_len), Type::Int(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would change sign from {} to {}",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if from_len > to_len {
-                    Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else if from_len < to_len {
-                    Ok(Expression::ZeroExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Casting value to uint
-            (Type::Value, Type::Uint(to_len)) => {
-                let from_len = ns.value_length * 8;
-                let to_len = *to_len as usize;
-
-                match from_len.cmp(&to_len) {
-                    Ordering::Greater => {
-                        if implicit {
-                            diagnostics.push(Diagnostic::cast_error(
-                                *loc,
-                                format!(
-                                    "implicit conversion would truncate from {} to {}",
-                                    from.to_string(ns),
-                                    to.to_string(ns)
-                                ),
-                            ));
-                            Err(())
-                        } else {
-                            Ok(Expression::Trunc {
-                                loc: *loc,
-                                to: to.clone(),
-                                expr: Box::new(self.clone()),
-                            })
-                        }
-                    }
-                    Ordering::Less => Ok(Expression::SignExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Equal => Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                }
-            }
-            (Type::Value, Type::Int(to_len)) => {
-                let from_len = ns.value_length * 8;
-                let to_len = *to_len as usize;
-
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would change sign from {} to {}",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if from_len > to_len {
-                    Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else if from_len < to_len {
-                    Ok(Expression::ZeroExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Casting value to uint
-            (Type::Uint(from_len), Type::Value) => {
-                let from_len = *from_len as usize;
-                let to_len = ns.value_length * 8;
-
-                match from_len.cmp(&to_len) {
-                    Ordering::Greater => {
-                        diagnostics.push(Diagnostic::cast_warning(
-                            *loc,
-                            format!(
-                                "conversion truncates {} to {}, as value is type {} on target {}",
-                                from.to_string(ns),
-                                to.to_string(ns),
-                                Type::Value.to_string(ns),
-                                ns.target
-                            ),
-                        ));
-
-                        Ok(Expression::CheckingTrunc {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(self.clone()),
-                        })
-                    }
-                    Ordering::Less => Ok(Expression::SignExt {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                    Ordering::Equal => Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    }),
-                }
-            }
-            // Casting int to address
-            (Type::Uint(from_len), Type::Address(_)) | (Type::Int(from_len), Type::Address(_)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion from {} to address not allowed",
-                            from.to_string(ns)
-                        ),
-                    ));
-
-                    Err(())
-                } else {
-                    // cast integer it to integer of the same size of address with sign ext etc
-                    let address_to_int = if from.is_signed_int() {
-                        Type::Int(address_bits)
-                    } else {
-                        Type::Uint(address_bits)
-                    };
-
-                    let expr = match from_len.cmp(&address_bits) {
-                        Ordering::Greater => Expression::Trunc {
-                            loc: *loc,
-                            to: address_to_int,
-                            expr: Box::new(self.clone()),
-                        },
-                        Ordering::Less if from.is_signed_int() => Expression::ZeroExt {
-                            loc: *loc,
-                            to: address_to_int,
-                            expr: Box::new(self.clone()),
-                        },
-                        Ordering::Less => Expression::SignExt {
-                            loc: *loc,
-                            to: address_to_int,
-                            expr: Box::new(self.clone()),
-                        },
-                        Ordering::Equal => self.clone(),
-                    };
-
-                    // Now cast integer to address
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(expr),
-                    })
-                }
-            }
-            // Casting address to int
-            (Type::Address(_), Type::Uint(to_len)) | (Type::Address(_), Type::Int(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-
-                    Err(())
-                } else {
-                    // first convert address to int/uint
-                    let address_to_int = if to.is_signed_int() {
-                        Type::Int(address_bits)
-                    } else {
-                        Type::Uint(address_bits)
-                    };
-
-                    let expr = Expression::Cast {
-                        loc: *loc,
-                        to: address_to_int,
-                        expr: Box::new(self.clone()),
-                    };
-                    // now resize int to request size with sign extension etc
-                    match to_len.cmp(&address_bits) {
-                        Ordering::Less => Ok(Expression::Trunc {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(expr),
-                        }),
-                        Ordering::Greater if to.is_signed_int() => Ok(Expression::ZeroExt {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(expr),
-                        }),
-                        Ordering::Greater => Ok(Expression::SignExt {
-                            loc: *loc,
-                            to: to.clone(),
-                            expr: Box::new(expr),
-                        }),
-                        Ordering::Equal => Ok(expr),
-                    }
-                }
-            }
-            // Lengthing or shorting a fixed bytes array
-            (Type::Bytes(from_len), Type::Bytes(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion would truncate from {} to {}",
-                            from.to_string(ns),
-                            to.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if to_len > from_len {
-                    let shift = (to_len - from_len) * 8;
-
-                    Ok(Expression::ShiftLeft {
-                        loc: *loc,
-                        ty: to.clone(),
-                        left: Box::new(Expression::ZeroExt {
-                            loc: self.loc(),
-                            to: to.clone(),
-                            expr: Box::new(self.clone()),
-                        }),
-                        right: Box::new(Expression::NumberLiteral {
-                            loc: *loc,
-                            ty: Type::Uint(*to_len as u16 * 8),
-                            value: BigInt::from_u8(shift).unwrap(),
-                        }),
-                    })
-                } else {
-                    let shift = (from_len - to_len) * 8;
-
-                    Ok(Expression::Trunc {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(Expression::ShiftRight {
-                            loc: self.loc(),
-                            ty: from.clone(),
-                            left: Box::new(self.clone()),
-                            right: Box::new(Expression::NumberLiteral {
-                                loc: self.loc(),
-                                ty: Type::Uint(*from_len as u16 * 8),
-                                value: BigInt::from_u8(shift).unwrap(),
-                            }),
-                            sign: false,
-                        }),
-                    })
-                }
-            }
-            (Type::Rational, Type::Uint(_) | Type::Int(_) | Type::Value) => {
-                match eval_const_rational(self, ns) {
-                    Ok((_, big_number)) => {
-                        if big_number.is_integer() {
-                            return Ok(Expression::Cast {
-                                loc: *loc,
-                                to: to.clone(),
-                                expr: Box::new(self.clone()),
-                            });
-                        }
-
-                        diagnostics.push(Diagnostic::cast_error(
-                            *loc,
-                            format!(
-                                "conversion to {} from {} not allowed",
-                                to.to_string(ns),
-                                from.to_string(ns)
-                            ),
-                        ));
-
-                        Err(())
-                    }
-                    Err(diag) => {
-                        diagnostics.push(diag);
-                        Err(())
-                    }
-                }
-            }
-            (Type::Uint(_) | Type::Int(_) | Type::Value, Type::Rational) => Ok(Expression::Cast {
-                loc: *loc,
-                to: to.clone(),
-                expr: Box::new(self.clone()),
-            }),
-            (Type::Bytes(_), Type::DynamicBytes) | (Type::DynamicBytes, Type::Bytes(_)) => {
-                Ok(Expression::BytesCast {
-                    loc: *loc,
-                    to: to.clone(),
-                    from: from.clone(),
-                    expr: Box::new(self.clone()),
-                })
-            }
-            // Explicit conversion from bytesN to int/uint only allowed with expliciy
-            // cast and if it is the same size (i.e. no conversion required)
-            (Type::Bytes(from_len), Type::Uint(to_len))
-            | (Type::Bytes(from_len), Type::Int(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if *from_len as u16 * 8 != *to_len {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Explicit conversion to bytesN from int/uint only allowed with expliciy
-            // cast and if it is the same size (i.e. no conversion required)
-            (Type::Uint(from_len), Type::Bytes(to_len))
-            | (Type::Int(from_len), Type::Bytes(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if *to_len as u16 * 8 != *from_len {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Explicit conversion from bytesN to address only allowed with expliciy
-            // cast and if it is the same size (i.e. no conversion required)
-            (Type::Bytes(from_len), Type::Address(_)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if *from_len as usize != ns.address_length {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Explicit conversion between contract and address is allowed
-            (Type::Address(false), Type::Address(true))
-            | (Type::Address(_), Type::Contract(_))
-            | (Type::Contract(_), Type::Address(_)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Conversion between contracts is allowed if it is a base
-            (Type::Contract(contract_no_from), Type::Contract(contract_no_to)) => {
-                if implicit && !is_base(*contract_no_to, *contract_no_from, ns) {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion not allowed since {} is not a base contract of {}",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // conversion from address payable to address is implicitly allowed (not vice versa)
-            (Type::Address(true), Type::Address(false)) => Ok(Expression::Cast {
-                loc: *loc,
-                to: to.clone(),
-                expr: Box::new(self.clone()),
-            }),
-            // Explicit conversion to bytesN from int/uint only allowed with expliciy
-            // cast and if it is the same size (i.e. no conversion required)
-            (Type::Address(_), Type::Bytes(to_len)) => {
-                if implicit {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "implicit conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if *to_len as usize != ns.address_length {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "conversion to {} from {} not allowed",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            (Type::String, Type::DynamicBytes) | (Type::DynamicBytes, Type::String)
-                if !implicit =>
-            {
-                Ok(Expression::Cast {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                })
-            }
-            // string conversions
-            // (Type::Bytes(_), Type::String) => Ok(Expression::Cast(self.loc(), to.clone(), Box::new(self.clone()))),
-            /*
-            (Type::String, Type::Bytes(to_len)) => {
-                if let Expression::BytesLiteral(_, from_str) = self {
-                    if from_str.len() > to_len as usize {
-                        diagnostics.push(Output::type_error(
-                            self.loc(),
-                            format!(
-                                "string of {} bytes is too long to fit into {}",
-                                from_str.len(),
-                                to.to_string(ns)
-                            ),
-                        ));
-                        return Err(());
-                    }
-                }
-                Ok(Expression::Cast(self.loc(), to.clone(), Box::new(self.clone()))
-            }
-            */
-            (Type::Void, _) => {
-                diagnostics.push(Diagnostic::cast_error(
-                    self.loc(),
-                    "function or method does not return a value".to_string(),
-                ));
-                Err(())
-            }
-            (
-                Type::ExternalFunction {
-                    params: from_params,
-                    mutability: from_mutablity,
-                    returns: from_returns,
-                },
-                Type::ExternalFunction {
-                    params: to_params,
-                    mutability: to_mutablity,
-                    returns: to_returns,
-                },
-            )
-            | (
-                Type::InternalFunction {
-                    params: from_params,
-                    mutability: from_mutablity,
-                    returns: from_returns,
-                },
-                Type::InternalFunction {
-                    params: to_params,
-                    mutability: to_mutablity,
-                    returns: to_returns,
-                },
-            ) => {
-                if from_params != to_params {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "function arguments do not match in conversion from '{}' to '{}'",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if from_returns != to_returns {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "function returns do not match in conversion from '{}' to '{}'",
-                            to.to_string(ns),
-                            from.to_string(ns)
-                        ),
-                    ));
-                    Err(())
-                } else if !compatible_mutability(from_mutablity, to_mutablity) {
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "function mutability not compatible in conversion from '{}' to '{}'",
-                            from.to_string(ns),
-                            to.to_string(ns),
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: Box::new(self.clone()),
-                    })
-                }
-            }
-            // Match any array with ArrayLength::AnyFixed if is it fixed for that dimension, and the
-            // element type and other dimensions also match
-            (Type::Array(from_elem, from_dim), Type::Array(to_elem, to_dim))
-                if from_elem == to_elem
-                    && from_dim.len() == to_dim.len()
-                    && from_dim.iter().zip(to_dim.iter()).all(|(f, t)| {
-                        f == t || matches!((f, t), (ArrayLength::Fixed(_), ArrayLength::AnyFixed))
-                    }) =>
-            {
-                Ok(self.clone())
-            }
-            (Type::DynamicBytes, Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
-                Ok(Expression::Cast {
-                    loc: *loc,
-                    to: to.clone(),
-                    expr: Box::new(self.clone()),
-                })
-            }
-            (Type::FunctionSelector, Type::Bytes(n)) => {
-                let selector_length = ns.target.selector_length();
-                if *n == selector_length {
-                    Ok(Expression::Cast {
-                        loc: *loc,
-                        to: to.clone(),
-                        expr: self.clone().into(),
-                    })
-                } else {
-                    if *n < selector_length {
-                        diagnostics.push(Diagnostic::warning(
-                            *loc,
-                            format!(
-                                "function selector should only be casted to bytes{} or larger",
-                                selector_length
-                            ),
-                        ));
-                    }
-                    self.cast_types(
-                        loc,
-                        &Type::Bytes(selector_length),
-                        to,
-                        implicit,
-                        ns,
-                        diagnostics,
-                    )
-                }
-            }
-            (Type::FunctionSelector, Type::Uint(n) | Type::Int(n)) => {
-                let selector_width = ns.target.selector_length() * 8;
-                if *n < selector_width as u16 {
-                    diagnostics.push(Diagnostic::warning(
-                        *loc,
-                        format!(
-                            "function selector needs an integer of at least {} bits to avoid being truncated",
-                            selector_width
-                        ),
-                    ));
-                }
-                self.cast_types(
-                    loc,
-                    &Type::Bytes(ns.target.selector_length()),
-                    to,
-                    implicit,
-                    ns,
-                    diagnostics,
-                )
-            }
-            _ => {
-                diagnostics.push(Diagnostic::cast_error(
-                    *loc,
-                    format!(
-                        "conversion from {} to {} not possible",
-                        from.to_string(ns),
-                        to.to_string(ns)
-                    ),
-                ));
-                Err(())
-            }
-        }
-    }
-}
-
-/// Unescape a string literal
-pub(crate) fn unescape(
-    literal: &str,
-    start: usize,
-    file_no: usize,
-    diagnostics: &mut Diagnostics,
-) -> Vec<u8> {
-    let mut s: Vec<u8> = Vec::new();
-    let mut indeces = literal.char_indices();
-
-    while let Some((_, ch)) = indeces.next() {
-        if ch != '\\' {
-            let mut buffer = [0; 4];
-            s.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
-            continue;
-        }
-
-        match indeces.next() {
-            Some((_, '\n')) => (),
-            Some((_, '\\')) => s.push(b'\\'),
-            Some((_, '\'')) => s.push(b'\''),
-            Some((_, '"')) => s.push(b'"'),
-            Some((_, 'b')) => s.push(b'\x08'),
-            Some((_, 'f')) => s.push(b'\x0c'),
-            Some((_, 'n')) => s.push(b'\n'),
-            Some((_, 'r')) => s.push(b'\r'),
-            Some((_, 't')) => s.push(b'\t'),
-            Some((_, 'v')) => s.push(b'\x0b'),
-            Some((i, 'x')) => match get_digits(&mut indeces, 2) {
-                Ok(ch) => s.push(ch as u8),
-                Err(offset) => {
-                    diagnostics.push(Diagnostic::error(
-                        pt::Loc::File(
-                            file_no,
-                            start + i,
-                            start + std::cmp::min(literal.len(), offset),
-                        ),
-                        "\\x escape should be followed by two hex digits".to_string(),
-                    ));
-                }
-            },
-            Some((i, 'u')) => match get_digits(&mut indeces, 4) {
-                Ok(codepoint) => match char::from_u32(codepoint) {
-                    Some(ch) => {
-                        let mut buffer = [0; 4];
-                        s.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
-                    }
-                    None => {
-                        diagnostics.push(Diagnostic::error(
-                            pt::Loc::File(file_no, start + i, start + i + 6),
-                            "Found an invalid unicode character".to_string(),
-                        ));
-                    }
-                },
-                Err(offset) => {
-                    diagnostics.push(Diagnostic::error(
-                        pt::Loc::File(
-                            file_no,
-                            start + i,
-                            start + std::cmp::min(literal.len(), offset),
-                        ),
-                        "\\u escape should be followed by four hex digits".to_string(),
-                    ));
-                }
-            },
-            Some((i, ch)) => {
-                diagnostics.push(Diagnostic::error(
-                    pt::Loc::File(file_no, start + i, start + i + ch.len_utf8()),
-                    format!("unknown escape character '{}'", ch),
-                ));
-            }
-            None => unreachable!(),
-        }
-    }
-    s
-}
-
-/// Get the hex digits for an escaped \x or \u. Returns either the value or
-/// or the offset of the last character
-fn get_digits(input: &mut std::str::CharIndices, len: usize) -> Result<u32, usize> {
-    let mut n: u32 = 0;
-    let offset;
-
-    for _ in 0..len {
-        if let Some((_, ch)) = input.next() {
-            if let Some(v) = ch.to_digit(16) {
-                n = (n << 4) + v;
-                continue;
-            }
-            offset = match input.next() {
-                Some((i, _)) => i,
-                None => std::usize::MAX,
-            };
-        } else {
-            offset = std::usize::MAX;
-        }
-
-        return Err(offset);
-    }
-
-    Ok(n)
-}
-
-fn coerce(
-    l: &Type,
-    l_loc: &pt::Loc,
-    r: &Type,
-    r_loc: &pt::Loc,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-) -> Result<Type, ()> {
-    let l = match l {
-        Type::Ref(ty) => ty,
-        Type::StorageRef(_, ty) => ty,
-        _ => l,
-    };
-    let r = match r {
-        Type::Ref(ty) => ty,
-        Type::StorageRef(_, ty) => ty,
-        _ => r,
-    };
-
-    if *l == *r {
-        return Ok(l.clone());
-    }
-
-    // Address payable is implicitly convertible to address, so we can compare these
-    if *l == Type::Address(false) && *r == Type::Address(true)
-        || *l == Type::Address(true) && *r == Type::Address(false)
-    {
-        return Ok(Type::Address(false));
-    }
-
-    coerce_number(l, l_loc, r, r_loc, true, false, ns, diagnostics)
-}
-
-fn get_int_length(
-    l: &Type,
-    l_loc: &pt::Loc,
-    allow_bytes: bool,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-) -> Result<(u16, bool), ()> {
-    match l {
-        Type::Uint(n) => Ok((*n, false)),
-        Type::Int(n) => Ok((*n, true)),
-        Type::Value => Ok((ns.value_length as u16 * 8, false)),
-        Type::Bytes(n) if allow_bytes => Ok((*n as u16 * 8, false)),
-        Type::Enum(n) => {
-            diagnostics.push(Diagnostic::error(
-                *l_loc,
-                format!("type enum {} not allowed", ns.enums[*n]),
-            ));
-            Err(())
-        }
-        Type::Struct(str_ty) => {
-            diagnostics.push(Diagnostic::error(
-                *l_loc,
-                format!("type struct {} not allowed", str_ty.definition(ns)),
-            ));
-            Err(())
-        }
-        Type::Array(..) => {
-            diagnostics.push(Diagnostic::error(
-                *l_loc,
-                format!("type array {} not allowed", l.to_string(ns)),
-            ));
-            Err(())
-        }
-        Type::Ref(n) => get_int_length(n, l_loc, allow_bytes, ns, diagnostics),
-        Type::StorageRef(_, n) => get_int_length(n, l_loc, allow_bytes, ns, diagnostics),
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *l_loc,
-                format!("expression of type {} not allowed", l.to_string(ns)),
-            ));
-            Err(())
-        }
-    }
-}
-
-pub fn coerce_number(
-    l: &Type,
-    l_loc: &pt::Loc,
-    r: &Type,
-    r_loc: &pt::Loc,
-    allow_bytes: bool,
-    for_compare: bool,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-) -> Result<Type, ()> {
-    let l = match l {
-        Type::Ref(ty) => ty,
-        Type::StorageRef(_, ty) => ty,
-        _ => l,
-    };
-    let r = match r {
-        Type::Ref(ty) => ty,
-        Type::StorageRef(_, ty) => ty,
-        _ => r,
-    };
-
-    match (l, r) {
-        (Type::Address(false), Type::Address(false)) if for_compare => {
-            return Ok(Type::Address(false));
-        }
-        (Type::Address(true), Type::Address(true)) if for_compare => {
-            return Ok(Type::Address(true));
-        }
-        (Type::Contract(left), Type::Contract(right)) if left == right && for_compare => {
-            return Ok(Type::Contract(*left));
-        }
-        (Type::Bytes(left_length), Type::Bytes(right_length)) if allow_bytes => {
-            return Ok(Type::Bytes(std::cmp::max(*left_length, *right_length)));
-        }
-        (Type::Bytes(_), _) if allow_bytes => {
-            return Ok(l.clone());
-        }
-        (_, Type::Bytes(_)) if allow_bytes => {
-            return Ok(r.clone());
-        }
-        (Type::Rational, Type::Int(_)) => {
-            return Ok(Type::Rational);
-        }
-        (Type::Rational, Type::Rational) => {
-            return Ok(Type::Rational);
-        }
-        (Type::Rational, Type::Uint(_)) => {
-            return Ok(Type::Rational);
-        }
-        (Type::Uint(_), Type::Rational) => {
-            return Ok(Type::Rational);
-        }
-        (Type::Int(_), Type::Rational) => {
-            return Ok(Type::Rational);
-        }
-        (Type::Bool, Type::Int(_) | Type::Uint(_)) => {
-            return Ok(r.clone());
-        }
-        (Type::Int(_) | Type::Uint(_), Type::Bool) => {
-            return Ok(l.clone());
-        }
-        _ => (),
-    }
-
-    let (left_len, left_signed) = get_int_length(l, l_loc, false, ns, diagnostics)?;
-
-    let (right_len, right_signed) = get_int_length(r, r_loc, false, ns, diagnostics)?;
-
-    Ok(match (left_signed, right_signed) {
-        (true, true) => Type::Int(left_len.max(right_len)),
-        (false, false) => Type::Uint(left_len.max(right_len)),
-        (true, false) => {
-            // uint8 fits into int16
-            let len = left_len.max(right_len + 8);
-
-            Type::Int(len.min(256))
-        }
-        (false, true) => {
-            // uint8 fits into int16
-            let len = (left_len + 8).max(right_len);
-
-            Type::Int(len.min(256))
-        }
-    })
-}
-
-/// Resolve the given number literal, multiplied by value of unit
-pub(super) fn number_literal(
-    loc: &pt::Loc,
-    integer: &str,
-    exp: &str,
-    ns: &Namespace,
-    unit: &BigInt,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let integer = BigInt::from_str(integer).unwrap();
-
-    let n = if exp.is_empty() {
-        integer
-    } else {
-        let base10 = BigInt::from_str("10").unwrap();
-
-        if let Some(abs_exp) = exp.strip_prefix('-') {
-            if let Ok(exp) = u8::from_str(abs_exp) {
-                let res = BigRational::new(integer, base10.pow(exp));
-
-                if res.is_integer() {
-                    res.to_integer()
-                } else {
-                    return Ok(Expression::RationalNumberLiteral {
-                        loc: *loc,
-                        ty: Type::Rational,
-                        value: res,
-                    });
-                }
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!("exponent '{}' too large", exp),
-                ));
-                return Err(());
-            }
-        } else if let Ok(exp) = u8::from_str(exp) {
-            integer.mul(base10.pow(exp))
-        } else {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("exponent '{}' too large", exp),
-            ));
-            return Err(());
-        }
-    };
-
-    bigint_to_expression(loc, &n.mul(unit), ns, diagnostics, resolve_to)
-}
-
-/// Resolve the given rational number literal, multiplied by value of unit
-fn rational_number_literal(
-    loc: &pt::Loc,
-    integer: &str,
-    fraction: &str,
-    exp: &str,
-    unit: &BigInt,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut integer = integer.to_owned();
-    let len = fraction.len();
-    let exp_negative = exp.starts_with('-');
-
-    let denominator = BigInt::from_str("10").unwrap().pow(len);
-    let zero_index = fraction
-        .chars()
-        .position(|c| c != '0')
-        .unwrap_or(usize::MAX);
-    let n = if exp.is_empty() {
-        if integer.is_empty() || integer == "0" {
-            if zero_index < usize::MAX {
-                BigRational::new(
-                    BigInt::from_str(&fraction[zero_index..]).unwrap(),
-                    denominator,
-                )
-            } else {
-                BigRational::from(BigInt::zero())
-            }
-        } else {
-            integer.push_str(fraction);
-            BigRational::new(BigInt::from_str(&integer).unwrap(), denominator)
-        }
-    } else {
-        let exp = if let Ok(exp) = u8::from_str(if exp_negative { &exp[1..] } else { exp }) {
-            exp
-        } else {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("exponent '{}' too large", exp),
-            ));
-            return Err(());
-        };
-        let exp_result = BigInt::from_str("10").unwrap().pow(exp);
-
-        if integer.is_empty() || integer == "0" {
-            if zero_index < usize::MAX {
-                if exp_negative {
-                    BigRational::new(
-                        BigInt::from_str(&fraction[zero_index..]).unwrap(),
-                        denominator.mul(exp_result),
-                    )
-                } else {
-                    BigRational::new(
-                        BigInt::from_str(&fraction[zero_index..])
-                            .unwrap()
-                            .mul(exp_result),
-                        denominator,
-                    )
-                }
-            } else {
-                BigRational::from(BigInt::zero())
-            }
-        } else {
-            integer.push_str(fraction);
-            if exp_negative {
-                BigRational::new(
-                    BigInt::from_str(&integer).unwrap(),
-                    denominator.mul(exp_result),
-                )
-            } else {
-                BigRational::new(
-                    BigInt::from_str(&integer).unwrap().mul(exp_result),
-                    denominator,
-                )
-            }
-        }
-    };
-
-    let res = n.mul(unit);
-
-    if res.is_integer() {
-        bigint_to_expression(loc, &res.to_integer(), ns, diagnostics, resolve_to)
-    } else {
-        Ok(Expression::RationalNumberLiteral {
-            loc: *loc,
-            ty: Type::Rational,
-            value: res,
-        })
-    }
-}
-
-/// Try to convert a BigInt into a Expression::NumberLiteral.
-pub fn bigint_to_expression(
-    loc: &pt::Loc,
-    n: &BigInt,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let bits = n.bits();
-
-    if let ResolveTo::Type(resolve_to) = resolve_to {
-        if *resolve_to != Type::Unresolved {
-            if !resolve_to.is_integer() {
-                diagnostics.push(Diagnostic::cast_error(
-                    *loc,
-                    format!("expected '{}', found integer", resolve_to.to_string(ns)),
-                ));
-                return Err(());
-            } else {
-                return Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: resolve_to.clone(),
-                    value: n.clone(),
-                });
-            }
-        }
-    }
-
-    // Return smallest type
-
-    let int_size = if bits < 7 { 8 } else { (bits + 7) & !7 } as u16;
-
-    if n.sign() == Sign::Minus {
-        if bits > 255 {
-            diagnostics.push(Diagnostic::error(*loc, format!("{} is too large", n)));
-            Err(())
-        } else {
-            Ok(Expression::NumberLiteral {
-                loc: *loc,
-                ty: Type::Int(int_size),
-                value: n.clone(),
-            })
-        }
-    } else if bits > 256 {
-        diagnostics.push(Diagnostic::error(*loc, format!("{} is too large", n)));
-        Err(())
-    } else {
-        Ok(Expression::NumberLiteral {
-            loc: *loc,
-            ty: Type::Uint(int_size),
-            value: n.clone(),
-        })
-    }
-}
-
-/// Compare two mutability levels
-pub fn compatible_mutability(left: &Mutability, right: &Mutability) -> bool {
-    matches!(
-        (left, right),
-        // only payable is compatible with payable
-        (Mutability::Payable(_), Mutability::Payable(_))
-            // default is compatible with anything but pure and view
-            | (Mutability::Nonpayable(_), Mutability::Nonpayable(_) | Mutability::Payable(_))
-            // view is compatible with anything but pure
-            | (Mutability::View(_), Mutability::View(_) | Mutability::Nonpayable(_) | Mutability::Payable(_))
-            // pure is compatible with anything
-            | (Mutability::Pure(_), _) // everything else is not compatible
-    )
-}
-
-/// When resolving an expression, what type are we looking for
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-pub enum ResolveTo<'a> {
-    Unknown,        // We don't know what we're looking for, best effort
-    Integer,        // Try to resolve to an integer type value (signed or unsigned, any bit width)
-    Discard,        // We won't be using the result. For example, an expression as a statement
-    Type(&'a Type), // We will be wanting this type please, e.g. `int64 x = 1;`
-}
-
-#[derive(Clone, Default)]
-pub struct ExprContext {
-    /// What source file are we in
-    pub file_no: usize,
-    // Are we resolving a contract, and if so, which one
-    pub contract_no: Option<usize>,
-    /// Are resolving the body of a function, and if so, which one
-    pub function_no: Option<usize>,
-    /// Are we currently in an unchecked block
-    pub unchecked: bool,
-    /// Are we evaluating a constant expression
-    pub constant: bool,
-    /// Are we resolving an l-value
-    pub lvalue: bool,
-    /// Are we resolving a yul function (it cannot have external dependencies)
-    pub yul_function: bool,
-}
-
-/// Resolve a parsed expression into an AST expression. The resolve_to argument is a hint to what
-/// type the result should be.
-pub fn expression(
-    expr: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    match expr {
-        pt::Expression::Parenthesis(_, expr) => {
-            expression(expr, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::ArrayLiteral(loc, exprs) => {
-            let res = array_literal(loc, exprs, context, ns, symtable, diagnostics, resolve_to);
-
-            if let Ok(exp) = &res {
-                used_variable(ns, exp, symtable);
-            }
-
-            res
-        }
-        pt::Expression::BoolLiteral(loc, v) => Ok(Expression::BoolLiteral {
-            loc: *loc,
-            value: *v,
-        }),
-        pt::Expression::StringLiteral(v) => {
-            Ok(string_literal(v, context.file_no, diagnostics, resolve_to))
-        }
-        pt::Expression::HexLiteral(v) => hex_literal(v, diagnostics, resolve_to),
-        pt::Expression::NumberLiteral(loc, integer, exp) => number_literal(
-            loc,
-            integer,
-            exp,
-            ns,
-            &BigInt::one(),
-            diagnostics,
-            resolve_to,
-        ),
-        pt::Expression::RationalNumberLiteral(loc, integer, fraction, exp) => {
-            rational_number_literal(
-                loc,
-                integer,
-                fraction,
-                exp,
-                &BigInt::one(),
-                ns,
-                diagnostics,
-                resolve_to,
-            )
-        }
-        pt::Expression::HexNumberLiteral(loc, n) => {
-            hex_number_literal(loc, n, ns, diagnostics, resolve_to)
-        }
-        pt::Expression::AddressLiteral(loc, address) => {
-            address_literal(loc, address, ns, diagnostics)
-        }
-        pt::Expression::Variable(id) => {
-            variable(id, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Add(loc, l, r) => {
-            addition(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Subtract(loc, l, r) => {
-            subtract(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::BitwiseOr(loc, l, r) => {
-            bitwise_or(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::BitwiseAnd(loc, l, r) => {
-            bitwise_and(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::BitwiseXor(loc, l, r) => {
-            bitwise_xor(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::ShiftLeft(loc, l, r) => {
-            shift_left(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::ShiftRight(loc, l, r) => {
-            shift_right(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Multiply(loc, l, r) => {
-            multiply(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Divide(loc, l, r) => {
-            divide(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Modulo(loc, l, r) => {
-            modulo(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
-        }
-        pt::Expression::Power(loc, b, e) => {
-            power(loc, b, e, context, ns, symtable, diagnostics, resolve_to)
-        }
-        // compare
-        pt::Expression::More(loc, l, r) => {
-            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-
-            check_var_usage_expression(ns, &left, &right, symtable);
-            let ty = coerce_number(
-                &left.ty(),
-                &l.loc(),
-                &right.ty(),
-                &r.loc(),
-                true,
-                true,
-                ns,
-                diagnostics,
-            )?;
-
-            let expr = Expression::More {
-                loc: *loc,
-                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-            };
-
-            if ty.is_rational() {
-                if let Err(diag) = eval_const_rational(&expr, ns) {
-                    diagnostics.push(diag);
-                }
-            }
-
-            Ok(expr)
-        }
-        pt::Expression::Less(loc, l, r) => {
-            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-
-            check_var_usage_expression(ns, &left, &right, symtable);
-
-            let ty = coerce_number(
-                &left.ty(),
-                &l.loc(),
-                &right.ty(),
-                &r.loc(),
-                true,
-                true,
-                ns,
-                diagnostics,
-            )?;
-
-            let expr = Expression::Less {
-                loc: *loc,
-                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-            };
-
-            if ty.is_rational() {
-                if let Err(diag) = eval_const_rational(&expr, ns) {
-                    diagnostics.push(diag);
-                }
-            }
-
-            Ok(expr)
-        }
-        pt::Expression::MoreEqual(loc, l, r) => {
-            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            check_var_usage_expression(ns, &left, &right, symtable);
-
-            let ty = coerce_number(
-                &left.ty(),
-                &l.loc(),
-                &right.ty(),
-                &r.loc(),
-                true,
-                true,
-                ns,
-                diagnostics,
-            )?;
-
-            let expr = Expression::MoreEqual {
-                loc: *loc,
-                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-            };
-
-            if ty.is_rational() {
-                if let Err(diag) = eval_const_rational(&expr, ns) {
-                    diagnostics.push(diag);
-                }
-            }
-
-            Ok(expr)
-        }
-        pt::Expression::LessEqual(loc, l, r) => {
-            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-            check_var_usage_expression(ns, &left, &right, symtable);
-
-            let ty = coerce_number(
-                &left.ty(),
-                &l.loc(),
-                &right.ty(),
-                &r.loc(),
-                true,
-                true,
-                ns,
-                diagnostics,
-            )?;
-
-            let expr = Expression::LessEqual {
-                loc: *loc,
-                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-            };
-
-            if ty.is_rational() {
-                if let Err(diag) = eval_const_rational(&expr, ns) {
-                    diagnostics.push(diag);
-                }
-            }
-
-            Ok(expr)
-        }
-        pt::Expression::Equal(loc, l, r) => equal(loc, l, r, context, ns, symtable, diagnostics),
-
-        pt::Expression::NotEqual(loc, l, r) => Ok(Expression::Not {
-            loc: *loc,
-            expr: Box::new(equal(loc, l, r, context, ns, symtable, diagnostics)?),
-        }),
-        // unary expressions
-        pt::Expression::Not(loc, e) => {
-            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-
-            used_variable(ns, &expr, symtable);
-            Ok(Expression::Not {
-                loc: *loc,
-                expr: Box::new(expr.cast(loc, &Type::Bool, true, ns, diagnostics)?),
-            })
-        }
-        pt::Expression::Complement(loc, e) => {
-            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-
-            used_variable(ns, &expr, symtable);
-            let expr_ty = expr.ty();
-
-            get_int_length(&expr_ty, loc, true, ns, diagnostics)?;
-
-            Ok(Expression::Complement {
-                loc: *loc,
-                ty: expr_ty,
-                expr: Box::new(expr),
-            })
-        }
-        pt::Expression::UnaryMinus(loc, e) => match e.as_ref() {
-            pt::Expression::NumberLiteral(_, integer, exp) => number_literal(
-                loc,
-                integer,
-                exp,
-                ns,
-                &BigInt::from(-1),
-                diagnostics,
-                resolve_to,
-            ),
-            pt::Expression::HexNumberLiteral(_, v) => {
-                // a hex literal with a minus before it cannot be an address literal or a bytesN value
-                let s: String = v.chars().skip(2).filter(|v| *v != '_').collect();
-
-                let n = BigInt::from_str_radix(&s, 16).unwrap();
-
-                bigint_to_expression(loc, &-n, ns, diagnostics, resolve_to)
-            }
-            pt::Expression::RationalNumberLiteral(loc, integer, fraction, exp) => {
-                rational_number_literal(
-                    loc,
-                    integer,
-                    fraction,
-                    exp,
-                    &BigInt::from(-1),
-                    ns,
-                    diagnostics,
-                    resolve_to,
-                )
-            }
-            e => {
-                let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-
-                used_variable(ns, &expr, symtable);
-                let expr_type = expr.ty();
-
-                if let Expression::NumberLiteral { value, .. } = expr {
-                    bigint_to_expression(loc, &-value, ns, diagnostics, resolve_to)
-                } else if let Expression::RationalNumberLiteral { ty, value: r, .. } = expr {
-                    Ok(Expression::RationalNumberLiteral {
-                        loc: *loc,
-                        ty,
-                        value: -r,
-                    })
-                } else {
-                    get_int_length(&expr_type, loc, false, ns, diagnostics)?;
-
-                    Ok(Expression::UnaryMinus {
-                        loc: *loc,
-                        ty: expr_type,
-                        expr: Box::new(expr),
-                    })
-                }
-            }
-        },
-        pt::Expression::UnaryPlus(loc, e) => {
-            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-            used_variable(ns, &expr, symtable);
-            let expr_type = expr.ty();
-
-            get_int_length(&expr_type, loc, false, ns, diagnostics)?;
-
-            Ok(expr)
-        }
-
-        pt::Expression::ConditionalOperator(loc, c, l, r) => {
-            let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-            let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-            check_var_usage_expression(ns, &left, &right, symtable);
-            let cond = expression(c, context, ns, symtable, diagnostics, resolve_to)?;
-            used_variable(ns, &cond, symtable);
-
-            let cond = cond.cast(&c.loc(), &Type::Bool, true, ns, diagnostics)?;
-
-            let ty = coerce(&left.ty(), &l.loc(), &right.ty(), &r.loc(), ns, diagnostics)?;
-            let left = left.cast(&l.loc(), &ty, true, ns, diagnostics)?;
-            let right = right.cast(&r.loc(), &ty, true, ns, diagnostics)?;
-
-            Ok(Expression::ConditionalOperator {
-                loc: *loc,
-                ty,
-                cond: Box::new(cond),
-                true_option: Box::new(left),
-                false_option: Box::new(right),
-            })
-        }
-
-        // pre/post decrement/increment
-        pt::Expression::PostIncrement(loc, var)
-        | pt::Expression::PreIncrement(loc, var)
-        | pt::Expression::PostDecrement(loc, var)
-        | pt::Expression::PreDecrement(loc, var) => {
-            if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "operator not allowed in constant context".to_string(),
-                ));
-                return Err(());
-            };
-
-            incr_decr(var, expr, context, ns, symtable, diagnostics)
-        }
-
-        // assignment
-        pt::Expression::Assign(loc, var, e) => {
-            if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "assignment not allowed in constant context".to_string(),
-                ));
-                return Err(());
-            };
-
-            assign_single(loc, var, e, context, ns, symtable, diagnostics)
-        }
-
-        pt::Expression::AssignAdd(loc, var, e)
-        | pt::Expression::AssignSubtract(loc, var, e)
-        | pt::Expression::AssignMultiply(loc, var, e)
-        | pt::Expression::AssignDivide(loc, var, e)
-        | pt::Expression::AssignModulo(loc, var, e)
-        | pt::Expression::AssignOr(loc, var, e)
-        | pt::Expression::AssignAnd(loc, var, e)
-        | pt::Expression::AssignXor(loc, var, e)
-        | pt::Expression::AssignShiftLeft(loc, var, e)
-        | pt::Expression::AssignShiftRight(loc, var, e) => {
-            if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "assignment not allowed in constant context".to_string(),
-                ));
-                return Err(());
-            };
-            let expr = assign_expr(loc, var, expr, e, context, ns, symtable, diagnostics);
-            if let Ok(expression) = &expr {
-                expression.recurse(ns, check_term_for_constant_overflow);
-            }
-            expr
-        }
-        pt::Expression::NamedFunctionCall(loc, ty, args) => named_call_expr(
-            loc,
-            ty,
-            args,
-            false,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            resolve_to,
-        ),
-        pt::Expression::New(loc, call) => {
-            if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    expr.loc(),
-                    "new not allowed in constant expression".to_string(),
-                ));
-                return Err(());
-            }
-
-            match call.remove_parenthesis() {
-                pt::Expression::FunctionCall(_, ty, args) => {
-                    let res = new(loc, ty, args, context, ns, symtable, diagnostics);
-
-                    if let Ok(exp) = &res {
-                        check_function_call(ns, exp, symtable);
-                    }
-                    res
-                }
-                pt::Expression::NamedFunctionCall(_, ty, args) => {
-                    let res =
-                        constructor_named_args(loc, ty, args, context, ns, symtable, diagnostics);
-
-                    if let Ok(exp) = &res {
-                        check_function_call(ns, exp, symtable);
-                    }
-
-                    res
-                }
-                pt::Expression::Variable(id) => {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!("missing constructor arguments to {}", id.name),
-                    ));
-                    Err(())
-                }
-                expr => {
-                    diagnostics.push(Diagnostic::error(
-                        expr.loc(),
-                        "type with arguments expected".into(),
-                    ));
-                    Err(())
-                }
-            }
-        }
-        pt::Expression::Delete(loc, _) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "delete not allowed in expression".to_string(),
-            ));
-            Err(())
-        }
-        pt::Expression::FunctionCall(loc, ty, args) => call_expr(
-            loc,
-            ty,
-            args,
-            false,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            resolve_to,
-        ),
-        pt::Expression::ArraySubscript(loc, _, None) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "expected expression before ']' token".to_string(),
-            ));
-
-            Err(())
-        }
-        pt::Expression::ArraySlice(loc, ..) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "slice not supported yet".to_string(),
-            ));
-
-            Err(())
-        }
-        pt::Expression::ArraySubscript(loc, array, Some(index)) => {
-            array_subscript(loc, array, index, context, ns, symtable, diagnostics)
-        }
-        pt::Expression::MemberAccess(loc, e, id) => member_access(
-            loc,
-            e.remove_parenthesis(),
-            id,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            resolve_to,
-        ),
-        pt::Expression::Or(loc, left, right) => {
-            let boolty = Type::Bool;
-            let l = expression(
-                left,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&boolty),
-            )?
-            .cast(loc, &boolty, true, ns, diagnostics)?;
-            let r = expression(
-                right,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&boolty),
-            )?
-            .cast(loc, &boolty, true, ns, diagnostics)?;
-
-            check_var_usage_expression(ns, &l, &r, symtable);
-
-            Ok(Expression::Or {
-                loc: *loc,
-                left: Box::new(l),
-                right: Box::new(r),
-            })
-        }
-        pt::Expression::And(loc, left, right) => {
-            let boolty = Type::Bool;
-            let l = expression(
-                left,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&boolty),
-            )?
-            .cast(loc, &boolty, true, ns, diagnostics)?;
-            let r = expression(
-                right,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&boolty),
-            )?
-            .cast(loc, &boolty, true, ns, diagnostics)?;
-            check_var_usage_expression(ns, &l, &r, symtable);
-
-            Ok(Expression::And {
-                loc: *loc,
-                left: Box::new(l),
-                right: Box::new(r),
-            })
-        }
-        pt::Expression::Type(loc, _) => {
-            diagnostics.push(Diagnostic::error(*loc, "type not expected".to_owned()));
-            Err(())
-        }
-        pt::Expression::List(loc, _) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "lists only permitted in destructure statements".to_owned(),
-            ));
-            Err(())
-        }
-        pt::Expression::FunctionCallBlock(loc, ..) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "unexpect block encountered".to_owned(),
-            ));
-            Err(())
-        }
-        pt::Expression::Unit(loc, expr, unit) => {
-            match unit {
-                pt::Unit::Wei(loc) | pt::Unit::Gwei(loc) | pt::Unit::Ether(loc)
-                    if ns.target != crate::Target::EVM =>
-                {
-                    diagnostics.push(Diagnostic::warning(
-                        *loc,
-                        "ethereum currency unit used while not targetting ethereum".to_owned(),
-                    ));
-                }
-                _ => (),
-            }
-
-            let unit = match unit {
-                pt::Unit::Seconds(_) => BigInt::from(1),
-                pt::Unit::Minutes(_) => BigInt::from(60),
-                pt::Unit::Hours(_) => BigInt::from(60 * 60),
-                pt::Unit::Days(_) => BigInt::from(60 * 60 * 24),
-                pt::Unit::Weeks(_) => BigInt::from(60 * 60 * 24 * 7),
-                pt::Unit::Wei(_) => BigInt::from(1),
-                pt::Unit::Gwei(_) => BigInt::from(10).pow(9u32),
-                pt::Unit::Ether(_) => BigInt::from(10).pow(18u32),
-            };
-
-            match expr.as_ref() {
-                pt::Expression::NumberLiteral(_, integer, exp) => {
-                    number_literal(loc, integer, exp, ns, &unit, diagnostics, resolve_to)
-                }
-                pt::Expression::RationalNumberLiteral(_, significant, mantissa, exp) => {
-                    rational_number_literal(
-                        loc,
-                        significant,
-                        mantissa,
-                        exp,
-                        &unit,
-                        ns,
-                        diagnostics,
-                        resolve_to,
-                    )
-                }
-                pt::Expression::HexNumberLiteral(loc, _) => {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        "hexadecimal numbers cannot be used with unit denominations".to_owned(),
-                    ));
-                    Err(())
-                }
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        "unit denominations can only be used with number literals".to_owned(),
-                    ));
-                    Err(())
-                }
-            }
-        }
-        pt::Expression::This(loc) => match context.contract_no {
-            Some(contract_no) => Ok(Expression::Builtin {
-                loc: *loc,
-                tys: vec![Type::Contract(contract_no)],
-                kind: Builtin::GetAddress,
-                args: Vec::new(),
-            }),
-            None => {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "this not allowed outside contract".to_owned(),
-                ));
-                Err(())
-            }
-        },
-    }
-}
-
-fn string_literal(
-    v: &[pt::StringLiteral],
-    file_no: usize,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Expression {
-    // Concatenate the strings
-    let mut result = Vec::new();
-    let mut loc = v[0].loc;
-
-    for s in v {
-        result.append(&mut unescape(
-            &s.string,
-            s.loc.start(),
-            file_no,
-            diagnostics,
-        ));
-        loc.use_end_from(&s.loc);
-    }
-
-    let length = result.len();
-
-    match resolve_to {
-        ResolveTo::Type(Type::String) => Expression::AllocDynamicBytes {
-            loc,
-            ty: Type::String,
-            length: Box::new(Expression::NumberLiteral {
-                loc,
-                ty: Type::Uint(32),
-                value: BigInt::from(length),
-            }),
-            init: Some(result),
-        },
-        ResolveTo::Type(Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
-            Expression::AllocDynamicBytes {
-                loc,
-                ty: Type::Slice(ty.clone()),
-                length: Box::new(Expression::NumberLiteral {
-                    loc,
-                    ty: Type::Uint(32),
-                    value: BigInt::from(length),
-                }),
-                init: Some(result),
-            }
-        }
-        _ => Expression::BytesLiteral {
-            loc,
-            ty: Type::Bytes(length as u8),
-            value: result,
-        },
-    }
-}
-
-fn hex_literal(
-    v: &[pt::HexLiteral],
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut result = Vec::new();
-    let mut loc = v[0].loc;
-
-    for s in v {
-        if (s.hex.len() % 2) != 0 {
-            diagnostics.push(Diagnostic::error(
-                s.loc,
-                format!("hex string \"{}\" has odd number of characters", s.hex),
-            ));
-            return Err(());
-        } else {
-            result.extend_from_slice(&hex::decode(&s.hex).unwrap());
-            loc.use_end_from(&s.loc);
-        }
-    }
-
-    let length = result.len();
-
-    match resolve_to {
-        ResolveTo::Type(Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
-            Ok(Expression::AllocDynamicBytes {
-                loc,
-                ty: Type::Slice(ty.clone()),
-                length: Box::new(Expression::NumberLiteral {
-                    loc,
-                    ty: Type::Uint(32),
-                    value: BigInt::from(length),
-                }),
-                init: Some(result),
-            })
-        }
-        ResolveTo::Type(Type::DynamicBytes) => Ok(Expression::AllocDynamicBytes {
-            loc,
-            ty: Type::DynamicBytes,
-            length: Box::new(Expression::NumberLiteral {
-                loc,
-                ty: Type::Uint(32),
-                value: BigInt::from(length),
-            }),
-            init: Some(result),
-        }),
-        _ => Ok(Expression::BytesLiteral {
-            loc,
-            ty: Type::Bytes(length as u8),
-            value: result,
-        }),
-    }
-}
-
-pub(super) fn hex_number_literal(
-    loc: &pt::Loc,
-    n: &str,
-    ns: &mut Namespace,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    // ns.address_length is in bytes; double for hex and two for the leading 0x
-    if n.starts_with("0x") && !n.chars().any(|c| c == '_') && n.len() == 42 {
-        let address = to_hexstr_eip55(n);
-
-        if ns.target == Target::EVM {
-            return if address == *n {
-                let s: String = address.chars().skip(2).collect();
-
-                Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: Type::Address(false),
-                    value: BigInt::from_str_radix(&s, 16).unwrap(),
-                })
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "address literal has incorrect checksum, expected '{}'",
-                        address
-                    ),
-                ));
-                Err(())
-            };
-        } else if address == *n {
-            // looks like ethereum address
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "ethereum address literal '{}' not supported on target {}",
-                    n, ns.target
-                ),
-            ));
-            return Err(());
-        }
-    }
-
-    // from_str_radix does not like the 0x prefix
-    let s: String = n.chars().skip(2).filter(|v| *v != '_').collect();
-
-    // hex values are allowed for bytesN but the length must match
-    if let ResolveTo::Type(Type::Bytes(length)) = resolve_to {
-        let expected_length = *length as usize * 2;
-        let val = BigInt::from_str_radix(&s, 16).unwrap();
-
-        return if !val.is_zero() && s.len() != expected_length {
-            diagnostics.push(Diagnostic::cast_error(
-                *loc,
-                format!(
-                    "hex literal {} must be {} digits for type 'bytes{}'",
-                    n, expected_length, length,
-                ),
-            ));
-            Err(())
-        } else {
-            Ok(Expression::NumberLiteral {
-                loc: *loc,
-                ty: Type::Bytes(*length),
-                value: val,
-            })
-        };
-    }
-
-    bigint_to_expression(
-        loc,
-        &BigInt::from_str_radix(&s, 16).unwrap(),
-        ns,
-        diagnostics,
-        resolve_to,
-    )
-}
-
-fn address_literal(
-    loc: &pt::Loc,
-    address: &str,
-    ns: &mut Namespace,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    if ns.target.is_substrate() {
-        match address.from_base58() {
-            Ok(v) => {
-                if v.len() != ns.address_length + 3 {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!(
-                            "address literal {} incorrect length of {}",
-                            address,
-                            v.len()
-                        ),
-                    ));
-                    return Err(());
-                }
-
-                let hash_data: Vec<u8> = b"SS58PRE"
-                    .iter()
-                    .chain(v[..=ns.address_length].iter())
-                    .cloned()
-                    .collect();
-
-                let hash = blake2_rfc::blake2b::blake2b(64, &[], &hash_data);
-                let hash = hash.as_bytes();
-
-                if v[ns.address_length + 1] != hash[0] || v[ns.address_length + 2] != hash[1] {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!("address literal {} hash incorrect checksum", address,),
-                    ));
-                    return Err(());
-                }
-
-                Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: Type::Address(false),
-                    value: BigInt::from_bytes_be(Sign::Plus, &v[1..ns.address_length + 1]),
-                })
-            }
-            Err(FromBase58Error::InvalidBase58Length) => {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!("address literal {} invalid base58 length", address),
-                ));
-                Err(())
-            }
-            Err(FromBase58Error::InvalidBase58Character(ch, pos)) => {
-                let mut loc = *loc;
-                if let pt::Loc::File(_, start, end) = &mut loc {
-                    *start += pos;
-                    *end = *start;
-                }
-                diagnostics.push(Diagnostic::error(
-                    loc,
-                    format!("address literal {} invalid character '{}'", address, ch),
-                ));
-                Err(())
-            }
-        }
-    } else if ns.target == Target::Solana {
-        match address.from_base58() {
-            Ok(v) => {
-                if v.len() != ns.address_length {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!(
-                            "address literal {} incorrect length of {}",
-                            address,
-                            v.len()
-                        ),
-                    ));
-                    Err(())
-                } else {
-                    Ok(Expression::NumberLiteral {
-                        loc: *loc,
-                        ty: Type::Address(false),
-                        value: BigInt::from_bytes_be(Sign::Plus, &v),
-                    })
-                }
-            }
-            Err(FromBase58Error::InvalidBase58Length) => {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!("address literal {} invalid base58 length", address),
-                ));
-                Err(())
-            }
-            Err(FromBase58Error::InvalidBase58Character(ch, pos)) => {
-                let mut loc = *loc;
-                if let pt::Loc::File(_, start, end) = &mut loc {
-                    *start += pos;
-                    *end = *start;
-                }
-                diagnostics.push(Diagnostic::error(
-                    loc,
-                    format!("address literal {} invalid character '{}'", address, ch),
-                ));
-                Err(())
-            }
-        }
-    } else {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!("address literal {} not supported on {}", address, ns.target),
-        ));
-        Err(())
-    }
-}
-
-fn variable(
-    id: &pt::Identifier,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    if let Some(v) = symtable.find(&id.name) {
-        return if context.constant {
-            diagnostics.push(Diagnostic::error(
-                id.loc,
-                format!("cannot read variable '{}' in constant expression", id.name),
-            ));
-            Err(())
-        } else {
-            Ok(Expression::Variable {
-                loc: id.loc,
-                ty: v.ty.clone(),
-                var_no: v.pos,
-            })
-        };
-    }
-
-    if let Some((builtin, ty)) = builtin::builtin_var(&id.loc, None, &id.name, ns, diagnostics) {
-        return Ok(Expression::Builtin {
-            loc: id.loc,
-            tys: vec![ty],
-            kind: builtin,
-            args: vec![],
-        });
-    }
-
-    // are we trying to resolve a function type?
-    let function_first = if let ResolveTo::Type(resolve_to) = resolve_to {
-        matches!(
-            resolve_to,
-            Type::InternalFunction { .. } | Type::ExternalFunction { .. }
-        )
-    } else {
-        false
-    };
-
-    match ns.resolve_var(context.file_no, context.contract_no, id, function_first) {
-        Some(Symbol::Variable(_, Some(var_contract_no), var_no)) => {
-            let var_contract_no = *var_contract_no;
-            let var_no = *var_no;
-
-            let var = &ns.contracts[var_contract_no].variables[var_no];
-
-            if var.constant {
-                Ok(Expression::ConstantVariable {
-                    loc: id.loc,
-                    ty: var.ty.clone(),
-                    contract_no: Some(var_contract_no),
-                    var_no,
-                })
-            } else if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    id.loc,
-                    format!(
-                        "cannot read contract variable '{}' in constant expression",
-                        id.name
-                    ),
-                ));
-                Err(())
-            } else {
-                Ok(Expression::StorageVariable {
-                    loc: id.loc,
-                    ty: Type::StorageRef(var.immutable, Box::new(var.ty.clone())),
-                    contract_no: var_contract_no,
-                    var_no,
-                })
-            }
-        }
-        Some(Symbol::Variable(_, None, var_no)) => {
-            let var_no = *var_no;
-
-            let var = &ns.constants[var_no];
-
-            Ok(Expression::ConstantVariable {
-                loc: id.loc,
-                ty: var.ty.clone(),
-                contract_no: None,
-                var_no,
-            })
-        }
-        Some(Symbol::Function(_)) => {
-            let mut name_matches = 0;
-            let mut expr = None;
-
-            for function_no in
-                available_functions(&id.name, true, context.file_no, context.contract_no, ns)
-            {
-                let func = &ns.functions[function_no];
-
-                if func.ty != pt::FunctionTy::Function {
-                    continue;
-                }
-
-                let ty = Type::InternalFunction {
-                    params: func.params.iter().map(|p| p.ty.clone()).collect(),
-                    mutability: func.mutability.clone(),
-                    returns: func.returns.iter().map(|p| p.ty.clone()).collect(),
-                };
-
-                name_matches += 1;
-                expr = Some(Expression::InternalFunction {
-                    loc: id.loc,
-                    ty,
-                    function_no,
-                    signature: if func.is_virtual || func.is_override.is_some() {
-                        Some(func.signature.clone())
-                    } else {
-                        None
-                    },
-                });
-            }
-
-            if name_matches == 1 {
-                Ok(expr.unwrap())
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    id.loc,
-                    format!("function '{}' is overloaded", id.name),
-                ));
-                Err(())
-            }
-        }
-        None if id.name == "now"
-            && matches!(
-                resolve_to,
-                ResolveTo::Type(Type::Uint(_)) | ResolveTo::Integer
-            ) =>
-        {
-            diagnostics.push(
-                Diagnostic::error(
-                    id.loc,
-                    "'now' not found. 'now' was an alias for 'block.timestamp' in older versions of the Solidity language. Please use 'block.timestamp' instead.".to_string(),
-                ));
-            Err(())
-        }
-        sym => {
-            diagnostics.push(Namespace::wrong_symbol(sym, id));
-            Err(())
-        }
-    }
-}
-
-fn subtract(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    if ty.is_rational() {
-        let expr = Expression::Subtract {
-            loc: *loc,
-            ty,
-            unchecked: false,
-            left: Box::new(left),
-            right: Box::new(right),
-        };
-
-        return match eval_const_rational(&expr, ns) {
-            Ok(_) => Ok(expr),
-            Err(diag) => {
-                diagnostics.push(diag);
-                Err(())
-            }
-        };
-    }
-
-    Ok(Expression::Subtract {
-        loc: *loc,
-        ty: ty.clone(),
-        unchecked: context.unchecked,
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn bitwise_or(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        true,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::BitwiseOr {
-        loc: *loc,
-        ty: ty.clone(),
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn bitwise_and(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        true,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::BitwiseAnd {
-        loc: *loc,
-        ty: ty.clone(),
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn bitwise_xor(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        true,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::BitwiseXor {
-        loc: *loc,
-        ty: ty.clone(),
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn shift_left(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-    // left hand side may be bytes/int/uint
-    // right hand size may be int/uint
-    let _ = get_int_length(&left.ty(), &l.loc(), true, ns, diagnostics)?;
-    let (right_length, _) = get_int_length(&right.ty(), &r.loc(), false, ns, diagnostics)?;
-
-    let left_type = left.ty().deref_any().clone();
-
-    Ok(Expression::ShiftLeft {
-        loc: *loc,
-        ty: left_type.clone(),
-        left: Box::new(left.cast(loc, &left_type, true, ns, diagnostics)?),
-        right: Box::new(cast_shift_arg(loc, right, right_length, &left_type, ns)),
-    })
-}
-
-fn shift_right(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let left_type = left.ty().deref_any().clone();
-    // left hand side may be bytes/int/uint
-    // right hand size may be int/uint
-    let _ = get_int_length(&left_type, &l.loc(), true, ns, diagnostics)?;
-    let (right_length, _) = get_int_length(&right.ty(), &r.loc(), false, ns, diagnostics)?;
-
-    Ok(Expression::ShiftRight {
-        loc: *loc,
-        ty: left_type.clone(),
-        left: Box::new(left.cast(loc, &left_type, true, ns, diagnostics)?),
-        right: Box::new(cast_shift_arg(loc, right, right_length, &left_type, ns)),
-        sign: left_type.is_signed_int(),
-    })
-}
-
-fn multiply(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    if ty.is_rational() {
-        let expr = Expression::Multiply {
-            loc: *loc,
-            ty,
-            unchecked: false,
-            left: Box::new(left),
-            right: Box::new(right),
-        };
-
-        return match eval_const_rational(&expr, ns) {
-            Ok(_) => Ok(expr),
-            Err(diag) => {
-                diagnostics.push(diag);
-                Err(())
-            }
-        };
-    }
-
-    // If we don't know what type the result is going to be, make any possible result fit.
-    if resolve_to == ResolveTo::Unknown {
-        let bits = std::cmp::min(256, ty.bits(ns) * 2);
-
-        if ty.is_signed_int() {
-            multiply(
-                loc,
-                l,
-                r,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&Type::Int(bits)),
-            )
-        } else {
-            multiply(
-                loc,
-                l,
-                r,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&Type::Uint(bits)),
-            )
-        }
-    } else {
-        Ok(Expression::Multiply {
-            loc: *loc,
-            ty: ty.clone(),
-            unchecked: context.unchecked,
-            left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-            right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-        })
-    }
-}
-
-fn divide(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::Divide {
-        loc: *loc,
-        ty: ty.clone(),
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn modulo(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    let ty = coerce_number(
-        &left.ty(),
-        &l.loc(),
-        &right.ty(),
-        &r.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::Modulo {
-        loc: *loc,
-        ty: ty.clone(),
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-fn power(
-    loc: &pt::Loc,
-    b: &pt::Expression,
-    e: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut base = expression(b, context, ns, symtable, diagnostics, resolve_to)?;
-
-    // If we don't know what type the result is going to be, assume
-    // the result is 256 bits
-    if resolve_to == ResolveTo::Unknown {
-        if base.ty().is_signed_int() {
-            base = expression(
-                b,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&Type::Int(256)),
-            )?;
-        } else {
-            base = expression(
-                b,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&Type::Uint(256)),
-            )?;
-        };
-    }
-
-    let exp = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-
-    check_var_usage_expression(ns, &base, &exp, symtable);
-
-    let base_type = base.ty();
-    let exp_type = exp.ty();
-
-    // solc-0.5.13 does not allow either base or exp to be signed
-    if base_type.is_signed_int() || exp_type.is_signed_int() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            "exponation (**) is not allowed with signed types".to_string(),
-        ));
-        return Err(());
-    }
-
-    let ty = coerce_number(
-        &base_type,
-        &b.loc(),
-        &exp_type,
-        &e.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    Ok(Expression::Power {
-        loc: *loc,
-        ty: ty.clone(),
-        unchecked: context.unchecked,
-        base: Box::new(base.cast(&b.loc(), &ty, true, ns, diagnostics)?),
-        exp: Box::new(exp.cast(&e.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-/// Resolve an new contract expression with positional arguments
-fn constructor(
-    loc: &pt::Loc,
-    no: usize,
-    args: &[pt::Expression],
-    call_args: CallArgs,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    // The current contract cannot be constructed with new. In order to create
-    // the contract, we need the code hash of the contract. Part of that code
-    // will be code we're emitted here. So we end up with a crypto puzzle.
-    let context_contract_no = match context.contract_no {
-        Some(n) if n == no => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "new cannot construct current contract '{}'",
-                    ns.contracts[no].name
-                ),
-            ));
-            return Err(());
-        }
-        Some(n) => n,
-        None => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "new contract not allowed in this context".to_string(),
-            ));
-            return Err(());
-        }
-    };
-
-    if !ns.contracts[no].instantiable {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "cannot construct '{}' of type '{}'",
-                ns.contracts[no].name, ns.contracts[no].ty
-            ),
-        ));
-
-        return Err(());
-    }
-
-    if ns.target == Target::Solana && ns.contracts[no].program_id.is_none() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "in order to instantiate contract '{}', a @program_id is required on contract '{}'",
-                ns.contracts[no].name, ns.contracts[no].name
-            ),
-        ));
-    }
-
-    // check for circular references
-    if circular_reference(no, context_contract_no, ns) {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "circular reference creating contract '{}'",
-                ns.contracts[no].name
-            ),
-        ));
-        return Err(());
-    }
-
-    if !ns.contracts[context_contract_no].creates.contains(&no) {
-        ns.contracts[context_contract_no].creates.push(no);
-    }
-
-    match match_constructor_to_args(loc, args, no, context, ns, symtable, diagnostics) {
-        Ok((constructor_no, cast_args)) => Ok(Expression::Constructor {
-            loc: *loc,
-            contract_no: no,
-            constructor_no,
-            args: cast_args,
-            call_args,
-        }),
-        Err(()) => Err(()),
-    }
-}
-
-/// Try and find constructor for arguments
-pub fn match_constructor_to_args(
-    loc: &pt::Loc,
-    args: &[pt::Expression],
-    contract_no: usize,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<(Option<usize>, Vec<Expression>), ()> {
-    let mut errors = Diagnostics::default();
-
-    // constructor call
-    let function_nos: Vec<usize> = ns.contracts[contract_no]
-        .functions
-        .iter()
-        .filter(|function_no| ns.functions[**function_no].is_constructor())
-        .copied()
-        .collect();
-
-    for function_no in &function_nos {
-        let mut matches = true;
-
-        let params_len = ns.functions[*function_no].params.len();
-
-        if params_len != args.len() {
-            errors.push(Diagnostic::cast_error(
-                *loc,
-                format!(
-                    "constructor expects {} arguments, {} provided",
-                    params_len,
-                    args.len()
-                ),
-            ));
-            matches = false;
-        }
-
-        let mut cast_args = Vec::new();
-
-        // resolve arguments for this constructor
-        for (i, arg) in args.iter().enumerate() {
-            let ty = ns.functions[*function_no]
-                .params
-                .get(i)
-                .map(|p| p.ty.clone());
-
-            let arg = match expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                &mut errors,
-                if let Some(ty) = &ty {
-                    ResolveTo::Type(ty)
-                } else {
-                    ResolveTo::Unknown
-                },
-            ) {
-                Ok(v) => v,
-                Err(()) => {
-                    matches = false;
-                    continue;
-                }
-            };
-
-            if let Some(ty) = &ty {
-                match arg.cast(&arg.loc(), ty, true, ns, &mut errors) {
-                    Ok(expr) => cast_args.push(expr),
-                    Err(()) => {
-                        matches = false;
-                    }
-                }
-            }
-        }
-
-        if matches {
-            return Ok((Some(*function_no), cast_args));
-        } else if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
-            return Err(());
-        }
-    }
-
-    match function_nos.len() {
-        0 if args.is_empty() => {
-            return Ok((None, Vec::new()));
-        }
-        0 | 1 => {
-            diagnostics.extend(errors);
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "cannot find overloaded constructor which matches signature".to_string(),
-            ));
-        }
-    }
-
-    Err(())
-}
-
-/// check if from creates to, recursively
-fn circular_reference(from: usize, to: usize, ns: &Namespace) -> bool {
-    if ns.contracts[from].creates.contains(&to) {
-        return true;
-    }
-
-    ns.contracts[from]
-        .creates
-        .iter()
-        .any(|n| circular_reference(*n, to, ns))
-}
-
-/// Resolve an new contract expression with named arguments
-pub fn constructor_named_args(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::NamedArgument],
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let (ty, call_args, _) = collect_call_args(ty, diagnostics)?;
-
-    let call_args = parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?;
-
-    let no = match ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)? {
-        Type::Contract(n) => n,
-        _ => {
-            diagnostics.push(Diagnostic::error(*loc, "contract expected".to_string()));
-            return Err(());
-        }
-    };
-
-    // The current contract cannot be constructed with new. In order to create
-    // the contract, we need the code hash of the contract. Part of that code
-    // will be code we're emitted here. So we end up with a crypto puzzle.
-    let context_contract_no = match context.contract_no {
-        Some(n) if n == no => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "new cannot construct current contract '{}'",
-                    ns.contracts[no].name
-                ),
-            ));
-            return Err(());
-        }
-        Some(n) => n,
-        None => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "new contract not allowed in this context".to_string(),
-            ));
-            return Err(());
-        }
-    };
-
-    if !ns.contracts[no].instantiable {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "cannot construct '{}' of type '{}'",
-                ns.contracts[no].name, ns.contracts[no].ty
-            ),
-        ));
-
-        return Err(());
-    }
-
-    // check for circular references
-    if circular_reference(no, context_contract_no, ns) {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "circular reference creating contract '{}'",
-                ns.contracts[no].name
-            ),
-        ));
-        return Err(());
-    }
-
-    if !ns.contracts[context_contract_no].creates.contains(&no) {
-        ns.contracts[context_contract_no].creates.push(no);
-    }
-
-    let mut arguments: BTreeMap<&str, &pt::Expression> = BTreeMap::new();
-
-    for arg in args {
-        if let Some(prev) = arguments.get(arg.name.name.as_str()) {
-            diagnostics.push(Diagnostic::error_with_note(
-                *loc,
-                format!("duplicate argument name '{}'", arg.name.name),
-                prev.loc(),
-                String::from("location of previous argument"),
-            ));
-            return Err(());
-        }
-        arguments.insert(&arg.name.name, &arg.expr);
-    }
-
-    let mut errors = Diagnostics::default();
-
-    // constructor call
-    let function_nos: Vec<usize> = ns.contracts[no]
-        .functions
-        .iter()
-        .filter(|function_no| ns.functions[**function_no].is_constructor())
-        .copied()
-        .collect();
-
-    // constructor call
-    for function_no in &function_nos {
-        let func = &ns.functions[*function_no];
-        let params_len = func.params.len();
-
-        let mut matches = true;
-
-        let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
-
-        if unnamed_params > 0 {
-            errors.push(Diagnostic::cast_error_with_note(
-                *loc,
-                format!(
-                    "constructor cannot be called with named arguments as {} of its parameters do not have names",
-                    unnamed_params,
-                ),
-                func.loc,
-                format!("definition of {}", func.ty),
-            ));
-            matches = false;
-        } else if params_len != args.len() {
-            errors.push(Diagnostic::cast_error_with_note(
-                *loc,
-                format!(
-                    "constructor expects {} arguments, {} provided",
-                    params_len,
-                    args.len()
-                ),
-                func.loc,
-                "definition of constructor".to_owned(),
-            ));
-            matches = false;
-        }
-
-        let mut cast_args = Vec::new();
-
-        let func_loc = ns.functions[*function_no].loc;
-
-        // check if arguments can be implicitly casted
-        for i in 0..params_len {
-            let param = ns.functions[*function_no].params[i].clone();
-
-            let arg = match arguments.get(param.name_as_str()) {
-                Some(a) => a,
-                None => {
-                    matches = false;
-                    errors.push(Diagnostic::cast_error_with_note(
-                        *loc,
-                        format!("missing argument '{}' to constructor", param.name_as_str()),
-                        func_loc,
-                        "definition of constructor".to_owned(),
-                    ));
-                    break;
-                }
-            };
-
-            let arg = match expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                &mut errors,
-                ResolveTo::Type(&param.ty),
-            ) {
-                Ok(e) => e,
-                Err(()) => {
-                    matches = false;
-                    continue;
-                }
-            };
-
-            match arg.cast(&arg.loc(), &param.ty, true, ns, &mut errors) {
-                Ok(expr) => cast_args.push(expr),
-                Err(()) => {
-                    matches = false;
-                }
-            }
-        }
-
-        if matches {
-            return Ok(Expression::Constructor {
-                loc: *loc,
-                contract_no: no,
-                constructor_no: Some(*function_no),
-                args: cast_args,
-                call_args,
-            });
-        } else if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
-            return Err(());
-        }
-    }
-
-    match function_nos.len() {
-        0 if args.is_empty() => Ok(Expression::Constructor {
-            loc: *loc,
-            contract_no: no,
-            constructor_no: None,
-            args: Vec::new(),
-            call_args,
-        }),
-        0 | 1 => {
-            diagnostics.extend(errors);
-
-            Err(())
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "cannot find overloaded constructor which matches signature".to_string(),
-            ));
-
-            Err(())
-        }
-    }
-}
-
-/// Resolve type(x).foo
-pub fn type_name_expr(
-    loc: &pt::Loc,
-    args: &[pt::Expression],
-    field: &pt::Identifier,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    if args.is_empty() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            "missing argument to type()".to_string(),
-        ));
-        return Err(());
-    }
-
-    if args.len() > 1 {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!("got {} arguments to type(), only one expected", args.len(),),
-        ));
-        return Err(());
-    }
-
-    let ty = ns.resolve_type(
-        context.file_no,
-        context.contract_no,
-        false,
-        &args[0],
-        diagnostics,
-    )?;
-
-    match (&ty, field.name.as_str()) {
-        (Type::Uint(_), "min") => {
-            bigint_to_expression(loc, &BigInt::zero(), ns, diagnostics, resolve_to)
-        }
-        (Type::Uint(bits), "max") => {
-            let max = BigInt::one().shl(*bits as usize).sub(1);
-            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to)
-        }
-        (Type::Int(bits), "min") => {
-            let min = BigInt::zero().sub(BigInt::one().shl(*bits as usize - 1));
-            bigint_to_expression(loc, &min, ns, diagnostics, resolve_to)
-        }
-        (Type::Int(bits), "max") => {
-            let max = BigInt::one().shl(*bits as usize - 1).sub(1);
-            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to)
-        }
-        (Type::Contract(n), "name") => Ok(Expression::BytesLiteral {
-            loc: *loc,
-            ty: Type::String,
-            value: ns.contracts[*n].name.as_bytes().to_vec(),
-        }),
-        (Type::Contract(n), "interfaceId") => {
-            let contract = &ns.contracts[*n];
-
-            if !contract.is_interface() {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "type(…).interfaceId is permitted on interface, not {} {}",
-                        contract.ty, contract.name
-                    ),
-                ));
-                Err(())
-            } else {
-                Ok(Expression::InterfaceId {
-                    loc: *loc,
-                    contract_no: *n,
-                })
-            }
-        }
-        (Type::Contract(no), "program_id") => {
-            let contract = &ns.contracts[*no];
-
-            if let Some(v) = &contract.program_id {
-                Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: Type::Address(false),
-                    value: BigInt::from_bytes_be(Sign::Plus, v),
-                })
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "{} '{}' has no declared program_id",
-                        contract.ty, contract.name
-                    ),
-                ));
-                Err(())
-            }
-        }
-        (Type::Contract(no), "creationCode") | (Type::Contract(no), "runtimeCode") => {
-            let contract_no = match context.contract_no {
-                Some(contract_no) => contract_no,
-                None => {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!(
-                            "type().{} not permitted outside of contract code",
-                            field.name
-                        ),
-                    ));
-                    return Err(());
-                }
-            };
-
-            // check for circular references
-            if *no == contract_no {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "containing our own contract code for '{}' would generate infinite size contract",
-                        ns.contracts[*no].name
-                    ),
-                ));
-                return Err(());
-            }
-
-            if circular_reference(*no, contract_no, ns) {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "circular reference creating contract code for '{}'",
-                        ns.contracts[*no].name
-                    ),
-                ));
-                return Err(());
-            }
-
-            if !ns.contracts[contract_no].creates.contains(no) {
-                ns.contracts[contract_no].creates.push(*no);
-            }
-
-            Ok(Expression::CodeLiteral {
-                loc: *loc,
-                contract_no: *no,
-                runtime: field.name == "runtimeCode",
-            })
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "type '{}' does not have type function {}",
-                    ty.to_string(ns),
-                    field.name
-                ),
-            ));
-            Err(())
-        }
-    }
-}
-
-/// Resolve an new expression
-pub fn new(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::Expression],
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
-
-    let ty = if let pt::Expression::New(_, ty) = ty.remove_parenthesis() {
-        ty
-    } else {
-        ty
-    };
-
-    let ty = ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)?;
-
-    match &ty {
-        Type::Array(ty, dim) => {
-            if matches!(dim.last(), Some(ArrayLength::Fixed(_))) {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "new cannot allocate fixed array type '{}'",
-                        ty.to_string(ns)
-                    ),
-                ));
-                return Err(());
-            }
-
-            if let Type::Contract(_) = ty.as_ref() {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!("new cannot construct array of '{}'", ty.to_string(ns)),
-                ));
-                return Err(());
-            }
-        }
-        Type::String | Type::DynamicBytes => {}
-        Type::Contract(n) => {
-            let call_args =
-                parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?;
-
-            return constructor(loc, *n, args, call_args, context, ns, symtable, diagnostics);
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("new cannot allocate type '{}'", ty.to_string(ns)),
-            ));
-            return Err(());
-        }
-    };
-
-    if let Some(loc) = call_args_loc {
-        diagnostics.push(Diagnostic::error(
-            loc,
-            "constructor arguments not permitted for allocation".to_string(),
-        ));
-        return Err(());
-    }
-
-    if args.len() != 1 {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            "new dynamic array should have a single length argument".to_string(),
-        ));
-        return Err(());
-    }
-
-    let size_loc = args[0].loc();
-    let expected_ty = Type::Uint(32);
-
-    let size_expr = expression(
-        &args[0],
-        context,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Type(&expected_ty),
-    )?;
-
-    used_variable(ns, &size_expr, symtable);
-
-    let size_ty = size_expr.ty();
-
-    if !matches!(size_ty.deref_any(), Type::Uint(_)) {
-        diagnostics.push(Diagnostic::error(
-            size_expr.loc(),
-            "new dynamic array should have an unsigned length argument".to_string(),
-        ));
-        return Err(());
-    }
-
-    let size = if size_ty.deref_any().bits(ns) > 32 {
-        diagnostics.push(Diagnostic::warning(
-            size_expr.loc(),
-            format!(
-                "conversion truncates {} to {}, as memory size is type {} on target {}",
-                size_ty.deref_any().to_string(ns),
-                expected_ty.to_string(ns),
-                expected_ty.to_string(ns),
-                ns.target
-            ),
-        ));
-
-        Expression::CheckingTrunc {
-            loc: size_loc,
-            to: expected_ty.clone(),
-            expr: Box::new(size_expr.cast(&size_loc, &size_ty, true, ns, diagnostics)?),
-        }
-    } else {
-        size_expr.cast(&size_loc, &expected_ty, true, ns, diagnostics)?
-    };
-
-    Ok(Expression::AllocDynamicBytes {
-        loc: *loc,
-        ty,
-        length: Box::new(size),
-        init: None,
-    })
-}
-
-/// Test for equality; first check string equality, then integer equality
-fn equal(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
-
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    // Comparing stringliteral against stringliteral
-    if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
-        (&left, &right)
-    {
-        return Ok(Expression::BoolLiteral {
-            loc: *loc,
-            value: l == r,
-        });
-    }
-
-    let left_type = left.ty();
-    let right_type = right.ty();
-
-    // compare string against literal
-    match (&left, &right_type.deref_any()) {
-        (Expression::BytesLiteral { value: l, .. }, Type::String)
-        | (Expression::BytesLiteral { value: l, .. }, Type::DynamicBytes) => {
-            return Ok(Expression::StringCompare {
-                loc: *loc,
-                left: StringLocation::RunTime(Box::new(right.cast(
-                    &r.loc(),
-                    right_type.deref_any(),
-                    true,
-                    ns,
-                    diagnostics,
-                )?)),
-                right: StringLocation::CompileTime(l.clone()),
-            });
-        }
-        _ => {}
-    }
-
-    match (&right, &left_type.deref_any()) {
-        (Expression::BytesLiteral { value, .. }, Type::String)
-        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
-            return Ok(Expression::StringCompare {
-                loc: *loc,
-                left: StringLocation::RunTime(Box::new(left.cast(
-                    &l.loc(),
-                    left_type.deref_any(),
-                    true,
-                    ns,
-                    diagnostics,
-                )?)),
-                right: StringLocation::CompileTime(value.clone()),
-            });
-        }
-        _ => {}
-    }
-
-    // compare string
-    match (&left_type.deref_any(), &right_type.deref_any()) {
-        (Type::String, Type::String) | (Type::DynamicBytes, Type::DynamicBytes) => {
-            return Ok(Expression::StringCompare {
-                loc: *loc,
-                left: StringLocation::RunTime(Box::new(left.cast(
-                    &l.loc(),
-                    left_type.deref_any(),
-                    true,
-                    ns,
-                    diagnostics,
-                )?)),
-                right: StringLocation::RunTime(Box::new(right.cast(
-                    &r.loc(),
-                    right_type.deref_any(),
-                    true,
-                    ns,
-                    diagnostics,
-                )?)),
-            });
-        }
-        _ => {}
-    }
-
-    let ty = coerce(&left_type, &l.loc(), &right_type, &r.loc(), ns, diagnostics)?;
-
-    let expr = Expression::Equal {
-        loc: *loc,
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    };
-
-    if ty.is_rational() {
-        if let Err(diag) = eval_const_rational(&expr, ns) {
-            diagnostics.push(diag);
-        }
-    }
-
-    Ok(expr)
-}
-
-/// Try string concatenation
-fn addition(
-    loc: &pt::Loc,
-    l: &pt::Expression,
-    r: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
-    let mut right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
-    check_var_usage_expression(ns, &left, &right, symtable);
-
-    // Concatenate stringliteral with stringliteral
-    if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
-        (&left, &right)
-    {
-        let mut c = Vec::with_capacity(l.len() + r.len());
-        c.extend_from_slice(l);
-        c.extend_from_slice(r);
-        let length = c.len();
-        return Ok(Expression::BytesLiteral {
-            loc: *loc,
-            ty: Type::Bytes(length as u8),
-            value: c,
-        });
-    }
-
-    let left_type = left.ty();
-    let right_type = right.ty();
-
-    // compare string against literal
-    match (&left, &right_type) {
-        (Expression::BytesLiteral { value, .. }, Type::String)
-        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
-            return Ok(Expression::StringConcat {
-                loc: *loc,
-                ty: right_type,
-                left: StringLocation::CompileTime(value.clone()),
-                right: StringLocation::RunTime(Box::new(right)),
-            });
-        }
-        _ => {}
-    }
-
-    match (&right, &left_type) {
-        (Expression::BytesLiteral { value, .. }, Type::String)
-        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
-            return Ok(Expression::StringConcat {
-                loc: *loc,
-                ty: left_type,
-                left: StringLocation::RunTime(Box::new(left)),
-                right: StringLocation::CompileTime(value.clone()),
-            });
-        }
-        _ => {}
-    }
-
-    // compare string
-    match (&left_type, &right_type) {
-        (Type::String, Type::String) | (Type::DynamicBytes, Type::DynamicBytes) => {
-            return Ok(Expression::StringConcat {
-                loc: *loc,
-                ty: right_type,
-                left: StringLocation::RunTime(Box::new(left)),
-                right: StringLocation::RunTime(Box::new(right)),
-            });
-        }
-        _ => {}
-    }
-
-    let ty = coerce_number(
-        &left_type,
-        &l.loc(),
-        &right_type,
-        &r.loc(),
-        false,
-        false,
-        ns,
-        diagnostics,
-    )?;
-
-    if ty.is_rational() {
-        let expr = Expression::Add {
-            loc: *loc,
-            ty,
-            unchecked: false,
-            left: Box::new(left),
-            right: Box::new(right),
-        };
-
-        return match eval_const_rational(&expr, ns) {
-            Ok(_) => Ok(expr),
-            Err(diag) => {
-                diagnostics.push(diag);
-                Err(())
-            }
-        };
-    }
-
-    // If we don't know what type the result is going to be
-    if resolve_to == ResolveTo::Unknown {
-        let bits = std::cmp::min(256, ty.bits(ns) * 2);
-        let resolve_to = if ty.is_signed_int() {
-            Type::Int(bits)
-        } else {
-            Type::Uint(bits)
-        };
-
-        left = expression(
-            l,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            ResolveTo::Type(&resolve_to),
-        )?;
-        right = expression(
-            r,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            ResolveTo::Type(&resolve_to),
-        )?;
-    }
-
-    Ok(Expression::Add {
-        loc: *loc,
-        ty: ty.clone(),
-        unchecked: context.unchecked,
-        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
-        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
-    })
-}
-
-/// Resolve an assignment
-fn assign_single(
-    loc: &pt::Loc,
-    left: &pt::Expression,
-    right: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let mut lcontext = context.clone();
-    lcontext.lvalue = true;
-
-    let var = expression(
-        left,
-        &lcontext,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Unknown,
-    )?;
-    assigned_variable(ns, &var, symtable);
-
-    let var_ty = var.ty();
-    let val = expression(
-        right,
-        context,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Type(var_ty.deref_any()),
-    )?;
-
-    val.recurse(ns, check_term_for_constant_overflow);
-
-    used_variable(ns, &val, symtable);
-    match &var {
-        Expression::ConstantVariable {
-            loc,
-            contract_no: Some(contract_no),
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "cannot assign to constant '{}'",
-                    ns.contracts[*contract_no].variables[*var_no].name
-                ),
-            ));
-            Err(())
-        }
-        Expression::ConstantVariable {
-            loc,
-            contract_no: None,
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
-            ));
-            Err(())
-        }
-        Expression::StorageVariable {
-            loc,
-            ty,
-            contract_no: var_contract_no,
-            var_no,
-        } => {
-            let store_var = &ns.contracts[*var_contract_no].variables[*var_no];
-
-            if store_var.immutable {
-                if let Some(function_no) = context.function_no {
-                    if !ns.functions[function_no].is_constructor() {
-                        diagnostics.push(Diagnostic::error(
-                            *loc,
-                            format!(
-                                "cannot assign to immutable '{}' outside of constructor",
-                                store_var.name
-                            ),
-                        ));
-                        return Err(());
-                    }
-                }
-            }
-
-            let ty = ty.deref_any();
-
-            Ok(Expression::Assign {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(var.clone()),
-                right: Box::new(val.cast(&right.loc(), ty, true, ns, diagnostics)?),
-            })
-        }
-        Expression::Variable { ty: var_ty, .. } => Ok(Expression::Assign {
-            loc: *loc,
-            ty: var_ty.clone(),
-            left: Box::new(var.clone()),
-            right: Box::new(val.cast(&right.loc(), var_ty, true, ns, diagnostics)?),
-        }),
-        _ => match &var_ty {
-            Type::Ref(r_ty) => Ok(Expression::Assign {
-                loc: *loc,
-                ty: *r_ty.clone(),
-                left: Box::new(var),
-                right: Box::new(val.cast(&right.loc(), r_ty, true, ns, diagnostics)?),
-            }),
-            Type::StorageRef(immutable, r_ty) => {
-                if *immutable {
-                    if let Some(function_no) = context.function_no {
-                        if !ns.functions[function_no].is_constructor() {
-                            diagnostics.push(Diagnostic::error(
-                                *loc,
-                                "cannot assign to immutable outside of constructor".to_string(),
-                            ));
-                            return Err(());
-                        }
-                    }
-                }
-
-                Ok(Expression::Assign {
-                    loc: *loc,
-                    ty: *r_ty.clone(),
-                    left: Box::new(var),
-                    right: Box::new(val.cast(&right.loc(), r_ty, true, ns, diagnostics)?),
-                })
-            }
-            _ => {
-                diagnostics.push(Diagnostic::error(
-                    var.loc(),
-                    "expression is not assignable".to_string(),
-                ));
-                Err(())
-            }
-        },
-    }
-}
-
-/// Resolve an assignment with an operator
-fn assign_expr(
-    loc: &pt::Loc,
-    left: &pt::Expression,
-    expr: &pt::Expression,
-    right: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let mut lcontext = context.clone();
-    lcontext.lvalue = true;
-
-    let var = expression(
-        left,
-        &lcontext,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Unknown,
-    )?;
-    assigned_variable(ns, &var, symtable);
-    let var_ty = var.ty();
-
-    let resolve_to = if matches!(
-        expr,
-        pt::Expression::AssignShiftLeft(..) | pt::Expression::AssignShiftRight(..)
-    ) {
-        ResolveTo::Unknown
-    } else {
-        ResolveTo::Type(var_ty.deref_any())
-    };
-
-    let set = expression(right, context, ns, symtable, diagnostics, resolve_to)?;
-    used_variable(ns, &set, symtable);
-    let set_type = set.ty();
-
-    let assign_operation = |assign: Expression,
-                            ty: &Type,
-                            ns: &Namespace,
-                            diagnostics: &mut Diagnostics|
-     -> Result<Expression, ()> {
-        let set = match expr {
-            pt::Expression::AssignShiftLeft(..) | pt::Expression::AssignShiftRight(..) => {
-                let left_length = get_int_length(ty, loc, true, ns, diagnostics)?;
-                let right_length = get_int_length(&set_type, &left.loc(), false, ns, diagnostics)?;
-
-                // TODO: does shifting by negative value need compiletime/runtime check?
-                if left_length == right_length {
-                    set
-                } else if right_length < left_length && set_type.is_signed_int() {
-                    Expression::SignExt {
-                        loc: *loc,
-                        to: ty.clone(),
-                        expr: Box::new(set),
-                    }
-                } else if right_length < left_length && !set_type.is_signed_int() {
-                    Expression::ZeroExt {
-                        loc: *loc,
-                        to: ty.clone(),
-                        expr: Box::new(set),
-                    }
-                } else {
-                    Expression::Trunc {
-                        loc: *loc,
-                        to: ty.clone(),
-                        expr: Box::new(set),
-                    }
-                }
-            }
-            _ => set.cast(&right.loc(), ty, true, ns, diagnostics)?,
-        };
-
-        Ok(match expr {
-            pt::Expression::AssignAdd(..) => Expression::Add {
-                loc: *loc,
-                ty: ty.clone(),
-                unchecked: context.unchecked,
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignSubtract(..) => Expression::Subtract {
-                loc: *loc,
-                ty: ty.clone(),
-                unchecked: context.unchecked,
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignMultiply(..) => Expression::Multiply {
-                loc: *loc,
-                ty: ty.clone(),
-                unchecked: context.unchecked,
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignOr(..) => Expression::BitwiseOr {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignAnd(..) => Expression::BitwiseAnd {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignXor(..) => Expression::BitwiseXor {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignShiftLeft(..) => Expression::ShiftLeft {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignShiftRight(..) => Expression::ShiftRight {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-                sign: ty.is_signed_int(),
-            },
-            pt::Expression::AssignDivide(..) => Expression::Divide {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            pt::Expression::AssignModulo(..) => Expression::Modulo {
-                loc: *loc,
-                ty: ty.clone(),
-                left: Box::new(assign),
-                right: Box::new(set),
-            },
-            _ => unreachable!(),
-        })
-    };
-
-    match &var {
-        Expression::ConstantVariable {
-            loc,
-            contract_no: Some(contract_no),
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "cannot assign to constant '{}'",
-                    ns.contracts[*contract_no].variables[*var_no].name
-                ),
-            ));
-            Err(())
-        }
-        Expression::ConstantVariable {
-            loc,
-            contract_no: None,
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
-            ));
-            Err(())
-        }
-        Expression::Variable { var_no, .. } => {
-            match var_ty {
-                Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => (),
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        var.loc(),
-                        format!(
-                            "variable '{}' of incorrect type {}",
-                            symtable.get_name(*var_no),
-                            var_ty.to_string(ns)
-                        ),
-                    ));
-                    return Err(());
-                }
-            };
-            Ok(Expression::Assign {
-                loc: *loc,
-                ty: var_ty.clone(),
-                left: Box::new(var.clone()),
-                right: Box::new(assign_operation(var, &var_ty, ns, diagnostics)?),
-            })
-        }
-        _ => match &var_ty {
-            Type::Ref(r_ty) => match r_ty.as_ref() {
-                Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => Ok(Expression::Assign {
-                    loc: *loc,
-                    ty: *r_ty.clone(),
-                    left: Box::new(var.clone()),
-                    right: Box::new(assign_operation(
-                        var.cast(loc, r_ty, true, ns, diagnostics)?,
-                        r_ty,
-                        ns,
-                        diagnostics,
-                    )?),
-                }),
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        var.loc(),
-                        format!("assigning to incorrect type {}", r_ty.to_string(ns)),
-                    ));
-                    Err(())
-                }
-            },
-            Type::StorageRef(immutable, r_ty) => {
-                if *immutable {
-                    if let Some(function_no) = context.function_no {
-                        if !ns.functions[function_no].is_constructor() {
-                            diagnostics.push(Diagnostic::error(
-                                *loc,
-                                "cannot assign to immutable outside of constructor".to_string(),
-                            ));
-                            return Err(());
-                        }
-                    }
-                }
-
-                match r_ty.as_ref() {
-                    Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => Ok(Expression::Assign {
-                        loc: *loc,
-                        ty: *r_ty.clone(),
-                        left: Box::new(var.clone()),
-                        right: Box::new(assign_operation(
-                            var.cast(loc, r_ty, true, ns, diagnostics)?,
-                            r_ty,
-                            ns,
-                            diagnostics,
-                        )?),
-                    }),
-                    _ => {
-                        diagnostics.push(Diagnostic::error(
-                            var.loc(),
-                            format!("assigning to incorrect type {}", r_ty.to_string(ns)),
-                        ));
-                        Err(())
-                    }
-                }
-            }
-            _ => {
-                diagnostics.push(Diagnostic::error(
-                    var.loc(),
-                    "expression is not assignable".to_string(),
-                ));
-                Err(())
-            }
-        },
-    }
-}
-
-/// Resolve an increment/decrement with an operator
-fn incr_decr(
-    v: &pt::Expression,
-    expr: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let op = |e: Expression, ty: Type| -> Expression {
-        match expr {
-            pt::Expression::PreIncrement(loc, _) => Expression::PreIncrement {
-                loc: *loc,
-                ty,
-                unchecked: context.unchecked,
-                expr: Box::new(e),
-            },
-            pt::Expression::PreDecrement(loc, _) => Expression::PreDecrement {
-                loc: *loc,
-                ty,
-                unchecked: context.unchecked,
-                expr: Box::new(e),
-            },
-            pt::Expression::PostIncrement(loc, _) => Expression::PostIncrement {
-                loc: *loc,
-                ty,
-                unchecked: context.unchecked,
-                expr: Box::new(e),
-            },
-            pt::Expression::PostDecrement(loc, _) => Expression::PostDecrement {
-                loc: *loc,
-                ty,
-                unchecked: context.unchecked,
-                expr: Box::new(e),
-            },
-            _ => unreachable!(),
-        }
-    };
-
-    let mut context = context.clone();
-
-    context.lvalue = true;
-
-    let var = expression(v, &context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
-    used_variable(ns, &var, symtable);
-    let var_ty = var.ty();
-
-    match &var {
-        Expression::ConstantVariable {
-            loc,
-            contract_no: Some(contract_no),
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "cannot assign to constant '{}'",
-                    ns.contracts[*contract_no].variables[*var_no].name
-                ),
-            ));
-            Err(())
-        }
-        Expression::ConstantVariable {
-            loc,
-            contract_no: None,
-            var_no,
-            ..
-        } => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
-            ));
-            Err(())
-        }
-        Expression::Variable { ty, var_no, .. } => {
-            match ty {
-                Type::Int(_) | Type::Uint(_) => (),
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        var.loc(),
-                        format!(
-                            "variable '{}' of incorrect type {}",
-                            symtable.get_name(*var_no),
-                            var_ty.to_string(ns)
-                        ),
-                    ));
-                    return Err(());
-                }
-            };
-            Ok(op(var.clone(), ty.clone()))
-        }
-        _ => match &var_ty {
-            Type::Ref(r_ty) => match r_ty.as_ref() {
-                Type::Int(_) | Type::Uint(_) => Ok(op(var, r_ty.as_ref().clone())),
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        var.loc(),
-                        format!("assigning to incorrect type {}", r_ty.to_string(ns)),
-                    ));
-                    Err(())
-                }
-            },
-            Type::StorageRef(immutable, r_ty) => {
-                if *immutable {
-                    if let Some(function_no) = context.function_no {
-                        if !ns.functions[function_no].is_constructor() {
-                            diagnostics.push(Diagnostic::error(
-                                var.loc(),
-                                "cannot assign to immutable outside of constructor".to_string(),
-                            ));
-                            return Err(());
-                        }
-                    }
-                }
-                match r_ty.as_ref() {
-                    Type::Int(_) | Type::Uint(_) => Ok(op(var, r_ty.as_ref().clone())),
-                    _ => {
-                        diagnostics.push(Diagnostic::error(
-                            var.loc(),
-                            format!("assigning to incorrect type {}", r_ty.to_string(ns)),
-                        ));
-                        Err(())
-                    }
-                }
-            }
-            _ => {
-                diagnostics.push(Diagnostic::error(
-                    var.loc(),
-                    "expression is not modifiable".to_string(),
-                ));
-                Err(())
-            }
-        },
-    }
-}
-
-/// Try to resolve expression as an enum value. An enum can be prefixed
-/// with import symbols, contract namespace before the enum type
-fn enum_value(
-    loc: &pt::Loc,
-    expr: &pt::Expression,
-    id: &pt::Identifier,
-    file_no: usize,
-    contract_no: Option<usize>,
-    ns: &Namespace,
-    diagnostics: &mut Diagnostics,
-) -> Result<Option<Expression>, ()> {
-    let mut namespace = Vec::new();
-
-    let mut expr = expr;
-
-    // the first element of the path is the deepest in the parse tree,
-    // so walk down and add to a list
-    while let pt::Expression::MemberAccess(_, member, name) = expr {
-        namespace.push(name);
-
-        expr = member.as_ref();
-    }
-
-    if let pt::Expression::Variable(name) = expr {
-        namespace.push(name);
-    } else {
-        return Ok(None);
-    }
-
-    // The leading part of the namespace can be import variables
-    let mut file_no = file_no;
-
-    // last element in our namespace vector is first element
-    while let Some(name) = namespace.last().map(|f| f.name.clone()) {
-        if let Some(Symbol::Import(_, import_file_no)) =
-            ns.variable_symbols.get(&(file_no, None, name))
-        {
-            file_no = *import_file_no;
-            namespace.pop();
-        } else {
-            break;
-        }
-    }
-
-    if namespace.is_empty() {
-        return Ok(None);
-    }
-
-    let mut contract_no = contract_no;
-
-    if let Some(no) = ns.resolve_contract(file_no, namespace.last().unwrap()) {
-        contract_no = Some(no);
-        namespace.pop();
-    }
-
-    if namespace.len() != 1 {
-        return Ok(None);
-    }
-
-    if let Some(e) = ns.resolve_enum(file_no, contract_no, namespace[0]) {
-        match ns.enums[e].values.get_full(&id.name) {
-            Some((val, _, _)) => Ok(Some(Expression::NumberLiteral {
-                loc: *loc,
-                ty: Type::Enum(e),
-                value: BigInt::from_usize(val).unwrap(),
-            })),
-            None => {
-                diagnostics.push(Diagnostic::error(
-                    id.loc,
-                    format!("enum {} does not have value {}", ns.enums[e], id.name),
-                ));
-                Err(())
-            }
-        }
-    } else {
-        Ok(None)
-    }
-}
-
-/// Resolve an member access expression
-fn member_access(
-    loc: &pt::Loc,
-    e: &pt::Expression,
-    id: &pt::Identifier,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    // is it a builtin special variable like "block.timestamp"
-    if let pt::Expression::Variable(namespace) = e {
-        if let Some((builtin, ty)) =
-            builtin::builtin_var(loc, Some(&namespace.name), &id.name, ns, diagnostics)
-        {
-            return Ok(Expression::Builtin {
-                loc: *loc,
-                tys: vec![ty],
-                kind: builtin,
-                args: vec![],
-            });
-        }
-
-        if builtin::builtin_namespace(&namespace.name) {
-            diagnostics.push(Diagnostic::error(
-                e.loc(),
-                format!("builtin '{}.{}' does not exist", namespace.name, id.name),
-            ));
-
-            return Err(());
-        }
-    }
-
-    // is it an enum value
-    if let Some(expr) = enum_value(
-        loc,
-        e,
-        id,
-        context.file_no,
-        context.contract_no,
-        ns,
-        diagnostics,
-    )? {
-        return Ok(expr);
-    }
-
-    // is it a constant (unless basecontract is a local variable)
-    if let Some(expr) = contract_constant(
-        loc,
-        e,
-        id,
-        context.file_no,
-        ns,
-        symtable,
-        diagnostics,
-        resolve_to,
-    )? {
-        return Ok(expr);
-    }
-
-    // is it a basecontract.function.selector expression (unless basecontract is a local variable)
-    if let pt::Expression::Variable(namespace) = e {
-        if symtable.find(&namespace.name).is_none() {
-            if let Some(call_contract_no) = ns.resolve_contract(context.file_no, namespace) {
-                // find function with this name
-                let mut name_matches = 0;
-                let mut expr = Err(());
-
-                for function_no in ns.contracts[call_contract_no].all_functions.keys() {
-                    let func = &ns.functions[*function_no];
-
-                    if func.name != id.name || func.ty != pt::FunctionTy::Function {
-                        continue;
-                    }
-
-                    name_matches += 1;
-
-                    expr = Ok(Expression::InternalFunction {
-                        loc: e.loc(),
-                        ty: function_type(func, false, resolve_to),
-                        function_no: *function_no,
-                        signature: None,
-                    })
-                }
-
-                return match name_matches {
-                    0 => {
-                        diagnostics.push(Diagnostic::error(
-                            e.loc(),
-                            format!(
-                                "contract '{}' does not have a member called '{}'",
-                                ns.contracts[call_contract_no].name, id.name,
-                            ),
-                        ));
-                        Err(())
-                    }
-                    1 => expr,
-                    _ => {
-                        diagnostics.push(Diagnostic::error(
-                            e.loc(),
-                            format!(
-                                "function '{}' of contract '{}' is overloaded",
-                                id.name, ns.contracts[call_contract_no].name,
-                            ),
-                        ));
-                        Err(())
-                    }
-                };
-            }
-        }
-    }
-
-    // is of the form "type(x).field", like type(c).min
-    if let pt::Expression::FunctionCall(_, name, args) = e {
-        if let pt::Expression::Variable(func_name) = name.as_ref() {
-            if func_name.name == "type" {
-                return type_name_expr(loc, args, id, context, ns, diagnostics, resolve_to);
-            }
-        }
-    }
-
-    let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
-    let expr_ty = expr.ty();
-
-    if let Type::Struct(struct_ty) = expr_ty.deref_memory() {
-        if let Some((i, f)) = struct_ty
-            .definition(ns)
-            .fields
-            .iter()
-            .enumerate()
-            .find(|f| id.name == f.1.name_as_str())
-        {
-            return if context.lvalue && f.readonly {
-                diagnostics.push(Diagnostic::error(
-                    id.loc,
-                    format!(
-                        "struct '{}' field '{}' is readonly",
-                        struct_ty.definition(ns),
-                        id.name
-                    ),
-                ));
-                Err(())
-            } else if f.readonly {
-                // readonly fields return the value, not a reference
-                Ok(Expression::StructMember {
-                    loc: id.loc,
-                    ty: f.ty.clone(),
-                    expr: Box::new(expr),
-                    field: i,
-                })
-            } else {
-                Ok(Expression::StructMember {
-                    loc: id.loc,
-                    ty: Type::Ref(Box::new(f.ty.clone())),
-                    expr: Box::new(expr),
-                    field: i,
-                })
-            };
-        } else {
-            diagnostics.push(Diagnostic::error(
-                id.loc,
-                format!(
-                    "struct '{}' does not have a field called '{}'",
-                    struct_ty.definition(ns),
-                    id.name
-                ),
-            ));
-            return Err(());
-        }
-    }
-
-    // Dereference if need to
-    let (expr, expr_ty) = if let Type::Ref(ty) = &expr_ty {
-        (
-            Expression::Load {
-                loc: *loc,
-                ty: expr_ty.clone(),
-                expr: Box::new(expr),
-            },
-            ty.as_ref().clone(),
-        )
-    } else {
-        (expr, expr_ty)
-    };
-
-    match expr_ty {
-        Type::Bytes(n) => {
-            if id.name == "length" {
-                //We should not eliminate an array from the code when 'length' is called
-                //So the variable is also assigned a value to be read from 'length'
-                assigned_variable(ns, &expr, symtable);
-                used_variable(ns, &expr, symtable);
-                return Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: Type::Uint(8),
-                    value: BigInt::from_u8(n).unwrap(),
-                });
-            }
-        }
-        Type::Array(_, dim) => {
-            if id.name == "length" {
-                return match dim.last().unwrap() {
-                    ArrayLength::Dynamic => Ok(Expression::Builtin {
-                        loc: *loc,
-                        tys: vec![Type::Uint(32)],
-                        kind: Builtin::ArrayLength,
-                        args: vec![expr],
-                    }),
-                    ArrayLength::Fixed(d) => {
-                        //We should not eliminate an array from the code when 'length' is called
-                        //So the variable is also assigned a value to be read from 'length'
-                        assigned_variable(ns, &expr, symtable);
-                        used_variable(ns, &expr, symtable);
-                        bigint_to_expression(
-                            loc,
-                            d,
-                            ns,
-                            diagnostics,
-                            ResolveTo::Type(&Type::Uint(32)),
-                        )
-                    }
-                    ArrayLength::AnyFixed => unreachable!(),
-                };
-            }
-        }
-        Type::String | Type::DynamicBytes => {
-            if id.name == "length" {
-                return Ok(Expression::Builtin {
-                    loc: *loc,
-                    tys: vec![Type::Uint(32)],
-                    kind: Builtin::ArrayLength,
-                    args: vec![expr],
-                });
-            }
-        }
-        Type::StorageRef(immutable, r) => match *r {
-            Type::Struct(str_ty) => {
-                return if let Some((field_no, field)) = str_ty
-                    .definition(ns)
-                    .fields
-                    .iter()
-                    .enumerate()
-                    .find(|(_, field)| id.name == field.name_as_str())
-                {
-                    Ok(Expression::StructMember {
-                        loc: id.loc,
-                        ty: Type::StorageRef(immutable, Box::new(field.ty.clone())),
-                        expr: Box::new(expr),
-                        field: field_no,
-                    })
-                } else {
-                    diagnostics.push(Diagnostic::error(
-                        id.loc,
-                        format!(
-                            "struct '{}' does not have a field called '{}'",
-                            str_ty.definition(ns).name,
-                            id.name
-                        ),
-                    ));
-                    Err(())
-                }
-            }
-            Type::Array(_, dim) => {
-                if id.name == "length" {
-                    let elem_ty = expr.ty().storage_array_elem().deref_into();
-
-                    if let Some(ArrayLength::Fixed(dim)) = dim.last() {
-                        // sparse array could be large than ns.storage_type() on Solana
-                        if dim.bits() > ns.storage_type().bits(ns) as u64 {
-                            return Ok(Expression::StorageArrayLength {
-                                loc: id.loc,
-                                ty: Type::Uint(256),
-                                array: Box::new(expr),
-                                elem_ty,
-                            });
-                        }
-                    }
-
-                    return Ok(Expression::StorageArrayLength {
-                        loc: id.loc,
-                        ty: ns.storage_type(),
-                        array: Box::new(expr),
-                        elem_ty,
-                    });
-                }
-            }
-            Type::Bytes(_) | Type::DynamicBytes | Type::String => {
-                if id.name == "length" {
-                    let elem_ty = expr.ty().storage_array_elem().deref_into();
-
-                    return Ok(Expression::StorageArrayLength {
-                        loc: id.loc,
-                        ty: Type::Uint(32),
-                        array: Box::new(expr),
-                        elem_ty,
-                    });
-                }
-            }
-            _ => {}
-        },
-        Type::Address(_) => {
-            if id.name == "balance" {
-                if ns.target.is_substrate() {
-                    let mut is_this = false;
-
-                    if let Expression::Cast { expr: this, .. } = &expr {
-                        if let Expression::Builtin {
-                            kind: Builtin::GetAddress,
-                            ..
-                        } = this.as_ref()
-                        {
-                            is_this = true;
-                        }
-                    }
-
-                    if !is_this {
-                        diagnostics.push(Diagnostic::error(
-                            expr.loc(),
-                            "substrate can only retrieve balance of this, like 'address(this).balance'".to_string(),
-                        ));
-                        return Err(());
-                    }
-                }
-                used_variable(ns, &expr, symtable);
-                return Ok(Expression::Builtin {
-                    loc: *loc,
-                    tys: vec![Type::Value],
-                    kind: Builtin::Balance,
-                    args: vec![expr],
-                });
-            }
-        }
-        Type::Contract(ref_contract_no) => {
-            let mut name_matches = 0;
-            let mut ext_expr = Err(());
-
-            for function_no in ns.contracts[ref_contract_no].all_functions.keys() {
-                let func = &ns.functions[*function_no];
-
-                if func.name != id.name || func.ty != pt::FunctionTy::Function || !func.is_public()
-                {
-                    continue;
-                }
-
-                let ty = Type::ExternalFunction {
-                    params: func.params.iter().map(|p| p.ty.clone()).collect(),
-                    mutability: func.mutability.clone(),
-                    returns: func.returns.iter().map(|p| p.ty.clone()).collect(),
-                };
-
-                name_matches += 1;
-                ext_expr = Ok(Expression::ExternalFunction {
-                    loc: id.loc,
-                    ty,
-                    address: Box::new(expr.clone()),
-                    function_no: *function_no,
-                });
-            }
-
-            return match name_matches {
-                0 => {
-                    diagnostics.push(Diagnostic::error(
-                        id.loc,
-                        format!(
-                            "{} '{}' has no public function '{}'",
-                            ns.contracts[ref_contract_no].ty,
-                            ns.contracts[ref_contract_no].name,
-                            id.name
-                        ),
-                    ));
-                    Err(())
-                }
-                1 => ext_expr,
-                _ => {
-                    diagnostics.push(Diagnostic::error(
-                        id.loc,
-                        format!(
-                            "function '{}' of {} '{}' is overloaded",
-                            id.name,
-                            ns.contracts[ref_contract_no].ty,
-                            ns.contracts[ref_contract_no].name
-                        ),
-                    ));
-                    Err(())
-                }
-            };
-        }
-        Type::ExternalFunction { .. } => {
-            if id.name == "address" {
-                used_variable(ns, &expr, symtable);
-                return Ok(Expression::Builtin {
-                    loc: e.loc(),
-                    tys: vec![Type::Address(false)],
-                    kind: Builtin::ExternalFunctionAddress,
-                    args: vec![expr],
-                });
-            }
-            if id.name == "selector" {
-                used_variable(ns, &expr, symtable);
-                return Ok(Expression::Builtin {
-                    loc: e.loc(),
-                    tys: vec![Type::FunctionSelector],
-                    kind: Builtin::FunctionSelector,
-                    args: vec![expr],
-                });
-            }
-        }
-        Type::InternalFunction { .. } => {
-            if let Expression::InternalFunction { .. } = expr {
-                if id.name == "selector" {
-                    used_variable(ns, &expr, symtable);
-                    return Ok(Expression::Builtin {
-                        loc: e.loc(),
-                        tys: vec![Type::FunctionSelector],
-                        kind: Builtin::FunctionSelector,
-                        args: vec![expr],
-                    });
-                }
-            }
-        }
-        _ => (),
-    }
-
-    diagnostics.push(Diagnostic::error(*loc, format!("'{}' not found", id.name)));
-
-    Err(())
-}
-
-fn contract_constant(
-    loc: &pt::Loc,
-    e: &pt::Expression,
-    id: &pt::Identifier,
-    file_no: usize,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Option<Expression>, ()> {
-    let namespace = match e {
-        pt::Expression::Variable(namespace) => namespace,
-        _ => return Ok(None),
-    };
-
-    if symtable.find(&namespace.name).is_some() {
-        return Ok(None);
-    }
-
-    if let Some(contract_no) = ns.resolve_contract(file_no, namespace) {
-        if let Some((var_no, var)) = ns.contracts[contract_no]
-            .variables
-            .iter_mut()
-            .enumerate()
-            .find(|(_, variable)| variable.name == id.name)
-        {
-            if !var.constant {
-                let resolve_function = if let ResolveTo::Type(ty) = resolve_to {
-                    matches!(
-                        ty,
-                        Type::InternalFunction { .. } | Type::ExternalFunction { .. }
-                    )
-                } else {
-                    false
-                };
-
-                if resolve_function {
-                    // requested function, fall through
-                    return Ok(None);
-                } else {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!(
-                            "need instance of contract '{}' to get variable value '{}'",
-                            ns.contracts[contract_no].name,
-                            ns.contracts[contract_no].variables[var_no].name,
-                        ),
-                    ));
-                    return Err(());
-                }
-            }
-
-            var.read = true;
-
-            return Ok(Some(Expression::ConstantVariable {
-                loc: *loc,
-                ty: var.ty.clone(),
-                contract_no: Some(contract_no),
-                var_no,
-            }));
-        }
-    }
-
-    Ok(None)
-}
-
-/// Resolve an array subscript expression
-fn array_subscript(
-    loc: &pt::Loc,
-    array: &pt::Expression,
-    index: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let mut array = expression(
-        array,
-        context,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Unknown,
-    )?;
-    let array_ty = array.ty();
-
-    if array.ty().is_mapping() {
-        return mapping_subscript(loc, array, index, context, ns, symtable, diagnostics);
-    }
-
-    let index_width_ty = if array_ty.is_contract_storage() && !array_ty.is_storage_bytes() {
-        Type::Uint(256)
-    } else {
-        Type::Uint(32)
-    };
-
-    let mut index = expression(
-        index,
-        context,
-        ns,
-        symtable,
-        diagnostics,
-        ResolveTo::Type(&index_width_ty),
-    )?;
-
-    let index_ty = index.ty();
-
-    index.recurse(ns, check_term_for_constant_overflow);
-
-    match index_ty.deref_any() {
-        Type::Uint(_) => (),
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "array subscript must be an unsigned integer, not '{}'",
-                    index.ty().to_string(ns)
-                ),
-            ));
-            return Err(());
-        }
-    };
-
-    if array_ty.is_storage_bytes() {
-        return Ok(Expression::Subscript {
-            loc: *loc,
-            ty: Type::StorageRef(false, Box::new(Type::Bytes(1))),
-            array_ty,
-            array: Box::new(array),
-            index: Box::new(index.cast(&index.loc(), &Type::Uint(32), false, ns, diagnostics)?),
-        });
-    }
-
-    // make sure we load the index value if needed
-    index = index.cast(&index.loc(), index_ty.deref_any(), true, ns, diagnostics)?;
-
-    let deref_ty = array_ty.deref_any();
-    match deref_ty {
-        Type::Bytes(_) | Type::Array(..) | Type::DynamicBytes => {
-            if array_ty.is_contract_storage() {
-                let elem_ty = array_ty.storage_array_elem();
-
-                // When subscripting a bytes32 type array, we need to load it. It is not
-                // assignable and the value is calculated by shifting in codegen
-                if let Type::Bytes(_) = deref_ty {
-                    array = array.cast(&array.loc(), deref_ty, true, ns, diagnostics)?;
-                }
-
-                Ok(Expression::Subscript {
-                    loc: *loc,
-                    ty: elem_ty,
-                    array_ty,
-                    array: Box::new(array),
-                    index: Box::new(index),
-                })
-            } else {
-                let elem_ty = array_ty.array_deref();
-
-                array = array.cast(
-                    &array.loc(),
-                    if array_ty.deref_memory().is_fixed_reference_type() {
-                        &array_ty
-                    } else {
-                        array_ty.deref_any()
-                    },
-                    true,
-                    ns,
-                    diagnostics,
-                )?;
-
-                Ok(Expression::Subscript {
-                    loc: *loc,
-                    ty: elem_ty,
-                    array_ty,
-                    array: Box::new(array),
-                    index: Box::new(index),
-                })
-            }
-        }
-        Type::String => {
-            diagnostics.push(Diagnostic::error(
-                array.loc(),
-                "array subscript is not permitted on string".to_string(),
-            ));
-            Err(())
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                array.loc(),
-                "expression is not an array".to_string(),
-            ));
-            Err(())
-        }
-    }
-}
-
-/// Resolve a function call with positional arguments
-fn struct_literal(
-    loc: &pt::Loc,
-    struct_ty: &StructType,
-    args: &[pt::Expression],
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let struct_def = struct_ty.definition(ns).clone();
-
-    let ty = Type::Struct(*struct_ty);
-
-    if ty.contains_builtins(ns, &StructType::AccountInfo).is_some() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "builtin struct '{}' cannot be created using struct literal",
-                struct_def.name,
-            ),
-        ));
-        Err(())
-    } else if args.len() != struct_def.fields.len() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "struct '{}' has {} fields, not {}",
-                struct_def.name,
-                struct_def.fields.len(),
-                args.len()
-            ),
-        ));
-        Err(())
-    } else {
-        let mut fields = Vec::new();
-
-        for (i, a) in args.iter().enumerate() {
-            let expr = expression(
-                a,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&struct_def.fields[i].ty),
-            )?;
-            used_variable(ns, &expr, symtable);
-            fields.push(expr.cast(loc, &struct_def.fields[i].ty, true, ns, diagnostics)?);
-        }
-
-        Ok(Expression::StructLiteral {
-            loc: *loc,
-            ty,
-            values: fields,
-        })
-    }
-}
-
-/// Resolve a function call via function type
-/// Function types do not have names so call cannot be using named parameters
-fn call_function_type(
-    loc: &pt::Loc,
-    expr: &pt::Expression,
-    args: &[pt::Expression],
-    call_args: &[&pt::NamedArgument],
-    call_args_loc: Option<pt::Loc>,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    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();
-
-        // check if arguments can be implicitly casted
-        for (i, arg) in args.iter().enumerate() {
-            let arg = expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&params[i]),
-            )?;
-
-            cast_args.push(arg.cast(&arg.loc(), &params[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, 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();
-
-        // check if arguments can be implicitly casted
-        for (i, arg) in args.iter().enumerate() {
-            let arg = expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                ResolveTo::Type(&params[i]),
-            )?;
-
-            cast_args.push(arg.cast(&arg.loc(), &params[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(())
-    }
-}
-
-/// Create a list of functions that can be called in this context. If global is true, then
-/// include functions outside of contracts
-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 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));
-        }
-    }
-
-    if let Some(contract_no) = contract_no {
-        list.extend(
-            ns.contracts[contract_no]
-                .all_functions
-                .keys()
-                .filter_map(|func_no| {
-                    if ns.functions[*func_no].name == name && ns.functions[*func_no].has_body {
-                        Some(*func_no)
-                    } else {
-                        None
-                    }
-                }),
-        );
-    }
-
-    list
-}
-
-/// Create a list of functions that can be called via super
-pub fn available_super_functions(name: &str, contract_no: usize, ns: &Namespace) -> Vec<usize> {
-    let mut list = Vec::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.name == name && func.has_body {
-                        Some(*func_no)
-                    } else {
-                        None
-                    }
-                }),
-        );
-    }
-
-    list
-}
-
-/// Resolve a function call with positional arguments
-pub fn function_call_pos_args(
-    loc: &pt::Loc,
-    id: &pt::Identifier,
-    func_ty: pt::FunctionTy,
-    args: &[pt::Expression],
-    function_nos: Vec<usize>,
-    virtual_call: bool,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    resolve_to: ResolveTo,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let mut name_matches = 0;
-    let mut errors = Diagnostics::default();
-
-    // Try to resolve as a function call
-    for function_no in &function_nos {
-        let func = &ns.functions[*function_no];
-
-        if func.ty != func_ty {
-            continue;
-        }
-
-        name_matches += 1;
-
-        let params_len = func.params.len();
-
-        if params_len != args.len() {
-            errors.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "{} expects {} arguments, {} provided",
-                    func.ty,
-                    params_len,
-                    args.len()
-                ),
-            ));
-            continue;
-        }
-
-        let mut matches = true;
-        let mut cast_args = Vec::new();
-
-        // check if arguments can be implicitly casted
-        for (i, arg) in args.iter().enumerate() {
-            let ty = ns.functions[*function_no].params[i].ty.clone();
-
-            let arg = match expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                &mut errors,
-                ResolveTo::Type(&ty),
-            ) {
-                Ok(e) => e,
-                Err(_) => {
-                    matches = false;
-                    continue;
-                }
-            };
-
-            match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
-                Ok(expr) => cast_args.push(expr),
-                Err(_) => {
-                    matches = false;
-                }
-            }
-        }
-
-        if !matches {
-            if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
-                return Err(());
-            }
-
-            continue;
-        }
-
-        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,
-                format!("declaration of {} '{}'", func.ty, func.name),
-            ));
-
-            continue;
-        }
-
-        let returns = function_returns(func, resolve_to);
-        let ty = function_type(func, false, resolve_to);
-
-        return Ok(Expression::InternalFunctionCall {
-            loc: *loc,
-            returns,
-            function: Box::new(Expression::InternalFunction {
-                loc: *loc,
-                ty,
-                function_no: *function_no,
-                signature: if virtual_call && (func.is_virtual || func.is_override.is_some()) {
-                    Some(func.signature.clone())
-                } else {
-                    None
-                },
-            }),
-            args: cast_args,
-        });
-    }
-
-    match name_matches {
-        0 => {
-            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),
-                ));
-            }
-        }
-        1 => diagnostics.extend(errors),
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!("cannot find overloaded {} which matches signature", func_ty),
-            ));
-        }
-    }
-
-    Err(())
-}
-
-/// Resolve a function call with named arguments
-fn function_call_named_args(
-    loc: &pt::Loc,
-    id: &pt::Identifier,
-    args: &[pt::NamedArgument],
-    function_nos: Vec<usize>,
-    virtual_call: bool,
-    context: &ExprContext,
-    resolve_to: ResolveTo,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let mut arguments = HashMap::new();
-
-    for arg in args {
-        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,
-            );
-        }
-
-        arguments.insert(arg.name.name.as_str(), &arg.expr);
-    }
-    // Try to resolve as a function call
-    let mut errors = Diagnostics::default();
-
-    // Try to resolve as a function call
-    for function_no in &function_nos {
-        let func = &ns.functions[*function_no];
-
-        if func.ty != pt::FunctionTy::Function {
-            continue;
-        }
-
-        let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
-        let params_len = func.params.len();
-        let mut matches = true;
-
-        if unnamed_params > 0 {
-            errors.push(Diagnostic::cast_error_with_note(
-                *loc,
-                format!(
-                    "function cannot be called with named arguments as {} of its parameters do not have names",
-                    unnamed_params,
-                ),
-                func.loc,
-                format!("definition of {}", func.name),
-            ));
-            matches = false;
-        } else if params_len != args.len() {
-            errors.push(Diagnostic::cast_error(
-                *loc,
-                format!(
-                    "function expects {} arguments, {} provided",
-                    params_len,
-                    args.len()
-                ),
-            ));
-            matches = false;
-        }
-
-        let mut cast_args = Vec::new();
-
-        // check if arguments can be implicitly casted
-        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 => {
-                    matches = false;
-                    diagnostics.push(Diagnostic::cast_error(
-                        *loc,
-                        format!(
-                            "missing argument '{}' to function '{}'",
-                            param.name_as_str(),
-                            id.name,
-                        ),
-                    ));
-                    continue;
-                }
-            };
-
-            let ty = param.ty.clone();
-
-            let arg = match expression(
-                arg,
-                context,
-                ns,
-                symtable,
-                &mut errors,
-                ResolveTo::Type(&ty),
-            ) {
-                Ok(e) => e,
-                Err(()) => {
-                    matches = false;
-                    continue;
-                }
-            };
-
-            match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
-                Ok(expr) => cast_args.push(expr),
-                Err(_) => {
-                    matches = false;
-                }
-            }
-        }
-
-        if !matches {
-            if diagnostics.extend_non_casting(&errors) {
-                return Err(());
-            }
-            continue;
-        }
-
-        let func = &ns.functions[*function_no];
-
-        if func.contract_no != context.contract_no && func.is_private() {
-            errors.push(Diagnostic::error_with_note(
-                *loc,
-                "cannot call private function".to_string(),
-                func.loc,
-                format!("declaration of function '{}'", func.name),
-            ));
-
-            continue;
-        }
-
-        let returns = function_returns(func, resolve_to);
-        let ty = function_type(func, false, resolve_to);
-
-        return Ok(Expression::InternalFunctionCall {
-            loc: *loc,
-            returns,
-            function: Box::new(Expression::InternalFunction {
-                loc: *loc,
-                ty,
-                function_no: *function_no,
-                signature: if virtual_call && (func.is_virtual || func.is_override.is_some()) {
-                    Some(func.signature.clone())
-                } else {
-                    None
-                },
-            }),
-            args: cast_args,
-        });
-    }
-
-    match function_nos.len() {
-        0 => {
-            diagnostics.push(Diagnostic::error(
-                id.loc,
-                format!("unknown function or type '{}'", id.name),
-            ));
-        }
-        1 => diagnostics.extend(errors),
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "cannot find overloaded function which matches signature".to_string(),
-            ));
-        }
-    }
-
-    Err(())
-}
-
-/// Resolve a struct literal with named fields
-fn named_struct_literal(
-    loc: &pt::Loc,
-    str_ty: &StructType,
-    args: &[pt::NamedArgument],
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let struct_def = str_ty.definition(ns).clone();
-    let ty = Type::Struct(*str_ty);
-
-    if ty.contains_builtins(ns, &StructType::AccountInfo).is_some() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "builtin struct '{}' cannot be created using struct literal",
-                struct_def.name,
-            ),
-        ));
-        Err(())
-    } else if args.len() != struct_def.fields.len() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "struct '{}' has {} fields, not {}",
-                struct_def.name,
-                struct_def.fields.len(),
-                args.len()
-            ),
-        ));
-        Err(())
-    } else {
-        let mut fields = Vec::new();
-        fields.resize(
-            args.len(),
-            Expression::BoolLiteral {
-                loc: Loc::Implicit,
-                value: false,
-            },
-        );
-        for a in args {
-            match struct_def.fields.iter().enumerate().find(|(_, f)| {
-                f.id.as_ref().map(|id| id.name.as_str()) == Some(a.name.name.as_str())
-            }) {
-                Some((i, f)) => {
-                    let expr = expression(
-                        &a.expr,
-                        context,
-                        ns,
-                        symtable,
-                        diagnostics,
-                        ResolveTo::Type(&f.ty),
-                    )?;
-                    used_variable(ns, &expr, symtable);
-                    fields[i] = expr.cast(loc, &f.ty, true, ns, diagnostics)?;
-                }
-                None => {
-                    diagnostics.push(Diagnostic::error(
-                        a.name.loc,
-                        format!(
-                            "struct '{}' has no field '{}'",
-                            struct_def.name, a.name.name,
-                        ),
-                    ));
-                    return Err(());
-                }
-            }
-        }
-        Ok(Expression::StructLiteral {
-            loc: *loc,
-            ty,
-            values: fields,
-        })
-    }
-}
-
-/// Resolve a method call with positional arguments
-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: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    if let pt::Expression::Variable(namespace) = var {
-        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 builtin::resolve_namespace_call(
-                loc,
-                &namespace.name,
-                &func.name,
-                args,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-            );
-        }
-
-        // is it a call to super
-        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_pos_args(
-                    loc,
-                    func,
-                    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(());
-            }
-        }
-
-        // library or base contract call
-        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_pos_args(
-                    loc,
-                    func,
-                    pt::FunctionTy::Function,
-                    args,
-                    available_functions(
-                        &func.name,
-                        false,
-                        context.file_no,
-                        Some(call_contract_no),
-                        ns,
-                    ),
-                    true,
-                    context,
-                    ns,
-                    resolve_to,
-                    symtable,
-                    diagnostics,
-                );
-            }
-
-            // is a base contract of us
-            if let Some(contract_no) = context.contract_no {
-                if is_base(call_contract_no, contract_no, ns) {
-                    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_pos_args(
-                        loc,
-                        func,
-                        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 {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        "function calls via contract name are only valid for base contracts".into(),
-                    ));
-                }
-            }
-        }
-    }
-
-    if let Ok(Type::UserType(no)) = ns.resolve_type(
-        context.file_no,
-        context.contract_no,
-        false,
-        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(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(Expression::Builtin {
-                    loc: *loc,
-                    tys: vec![user_ty],
-                    kind: Builtin::UserTypeWrap,
-                    args: vec![expr.cast(&expr.loc(), &elem_ty, true, ns, diagnostics)?],
-                })
-            };
-        }
-    }
-
-    if let Some(mut path) = ns.expr_to_identifier_path(var) {
-        path.identifiers.push(func.clone());
-
-        if let Ok(list) = ns.resolve_free_function_with_namespace(
-            context.file_no,
-            &path,
-            &mut Diagnostics::default(),
-        ) {
-            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,
-                func,
-                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(expr) =
-        builtin::resolve_method_call(&var_expr, func, args, context, ns, symtable, diagnostics)?
-    {
-        return Ok(expr);
-    }
-
-    let var_ty = var_expr.ty();
-
-    if matches!(var_ty, Type::Bytes(_) | Type::String) && 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(());
-            }
-
-            string_format(loc, bs, args, context, ns, symtable, diagnostics)
-        } else {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "format only allowed on string literals".to_string(),
-            ));
-            Err(())
-        };
-    }
-
-    if let Type::StorageRef(immutable, ty) = &var_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];
-
-                    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(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(Expression::Builtin {
-                        loc: func.loc,
-                        tys: vec![return_ty],
-                        kind: Builtin::ArrayPop,
-                        args: vec![var_expr],
-                    });
-                }
-            }
-            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];
-
-                    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(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(Expression::Builtin {
-                        loc: func.loc,
-                        tys: vec![Type::Bytes(1)],
-                        kind: Builtin::ArrayPop,
-                        args: vec![var_expr],
-                    });
-                }
-            }
-            _ => {}
-        }
-    }
-
-    if matches!(var_ty, Type::Array(..) | Type::DynamicBytes) {
-        if func.name == "push" {
-            let elem_ty = var_ty.array_elem();
-
-            let val = match args.len() {
-                0 => {
-                    return Ok(Expression::Builtin {
-                        loc: *loc,
-                        tys: vec![elem_ty.clone()],
-                        kind: Builtin::ArrayPush,
-                        args: vec![var_expr],
-                    });
-                }
-                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(Expression::Builtin {
-                loc: *loc,
-                tys: vec![elem_ty.clone()],
-                kind: Builtin::ArrayPush,
-                args: vec![var_expr, 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(Expression::Builtin {
-                loc: *loc,
-                tys: vec![elem_ty.clone()],
-                kind: Builtin::ArrayPop,
-                args: vec![var_expr],
-            });
-        }
-    }
-
-    if let Type::Contract(ext_contract_no) = &var_ty.deref_any() {
-        let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?;
-
-        let mut errors = Diagnostics::default();
-        let mut name_matches: Vec<usize> = Vec::new();
-
-        for function_no in ns.contracts[*ext_contract_no].all_functions.keys() {
-            if func.name != ns.functions[*function_no].name
-                || ns.functions[*function_no].ty != pt::FunctionTy::Function
-            {
-                continue;
-            }
-
-            name_matches.push(*function_no);
-        }
-
-        for function_no in &name_matches {
-            let params_len = ns.functions[*function_no].params.len();
-
-            if params_len != args.len() {
-                errors.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "function expects {} arguments, {} provided",
-                        params_len,
-                        args.len()
-                    ),
-                ));
-                continue;
-            }
-
-            let mut matches = true;
-            let mut cast_args = Vec::new();
-
-            // check if arguments can be implicitly casted
-            for (i, arg) in args.iter().enumerate() {
-                let ty = ns.functions[*function_no].params[i].ty.clone();
-
-                let arg = match expression(
-                    arg,
-                    context,
-                    ns,
-                    symtable,
-                    &mut errors,
-                    ResolveTo::Type(&ty),
-                ) {
-                    Ok(e) => e,
-                    Err(_) => {
-                        matches = false;
-                        continue;
-                    }
-                };
-
-                match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
-                    Ok(expr) => cast_args.push(expr),
-                    Err(()) => {
-                        matches = false;
-                        continue;
-                    }
-                }
-            }
-
-            if matches {
-                if !ns.functions[*function_no].is_public() {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!("function '{}' is not 'public' or 'external'", func.name),
-                    ));
-                    return Err(());
-                }
-
-                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);
-
-                return Ok(Expression::ExternalFunctionCall {
-                    loc: *loc,
-                    returns,
-                    function: Box::new(Expression::ExternalFunction {
-                        loc: *loc,
-                        ty,
-                        function_no: *function_no,
-                        address: Box::new(var_expr.cast(
-                            &var.loc(),
-                            &Type::Contract(func.contract_no.unwrap()),
-                            true,
-                            ns,
-                            diagnostics,
-                        )?),
-                    }),
-                    args: cast_args,
-                    call_args,
-                });
-            } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) {
-                return Err(());
-            }
-        }
-
-        // what about call args
-        match using::try_resolve_using_call(
-            loc,
-            func,
-            &var_expr,
-            context,
-            args,
-            symtable,
-            diagnostics,
-            ns,
-            resolve_to,
-        ) {
-            Ok(Some(expr)) => {
-                return Ok(expr);
-            }
-            Ok(None) => (),
-            Err(_) => {
-                return Err(());
-            }
-        }
-
-        if name_matches.len() == 1 {
-            diagnostics.extend(errors);
-        } else if name_matches.len() != 1 {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "cannot find overloaded function which matches signature".to_string(),
-            ));
-        }
-
-        return Err(());
-    }
-
-    if let Type::Address(is_payable) = &var_ty.deref_any() {
-        if func.name == "transfer" || func.name == "send" {
-            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(Expression::Builtin {
-                    loc: *loc,
-                    tys: vec![Type::Void],
-                    kind: Builtin::PayableTransfer,
-                    args: vec![address, value],
-                })
-            } else {
-                Ok(Expression::Builtin {
-                    loc: *loc,
-                    tys: vec![Type::Bool],
-                    kind: Builtin::PayableSend,
-                    args: vec![address, value],
-                })
-            };
-        }
-    }
-
-    if let Type::Address(payable) = &var_ty.deref_any() {
-        let ty = match func.name.as_str() {
-            "call" => Some(CallTy::Regular),
-            "delegatecall" if ns.target == Target::EVM => 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, true, context, ns, symtable, diagnostics)?;
-
-            if ty != CallTy::Regular && call_args.value.is_some() {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!("'{}' cannot have value specifed", func.name,),
-                ));
-
-                return Err(());
-            }
-
-            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(Expression::ExternalFunctionCallRaw {
-                loc: *loc,
-                ty,
-                args: Box::new(args),
-                address: Box::new(var_expr.cast(
-                    &var_expr.loc(),
-                    &Type::Address(*payable),
-                    true,
-                    ns,
-                    diagnostics,
-                )?),
-                call_args,
-            });
-        }
-    }
-
-    // resolve it using library extension
-    match using::try_resolve_using_call(
-        loc,
-        func,
-        &var_expr,
-        context,
-        args,
-        symtable,
-        diagnostics,
-        ns,
-        resolve_to,
-    ) {
-        Ok(Some(expr)) => {
-            return Ok(expr);
-        }
-        Ok(None) => (),
-        Err(_) => {
-            return Err(());
-        }
-    }
-
-    diagnostics.push(Diagnostic::error(
-        func.loc,
-        format!("method '{}' does not exist", func.name),
-    ));
-
-    Err(())
-}
-
-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: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    if let pt::Expression::Variable(namespace) = var {
-        // is it a call to super
-        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,
-                    func_name,
-                    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(());
-            }
-        }
-
-        // library or base contract call
-        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,
-                    func_name,
-                    args,
-                    available_functions(
-                        &func_name.name,
-                        false,
-                        context.file_no,
-                        Some(call_contract_no),
-                        ns,
-                    ),
-                    true,
-                    context,
-                    resolve_to,
-                    ns,
-                    symtable,
-                    diagnostics,
-                );
-            }
-
-            // is a base contract of us
-            if let Some(contract_no) = context.contract_no {
-                if is_base(call_contract_no, contract_no, ns) {
-                    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,
-                        func_name,
-                        args,
-                        available_functions(
-                            &func_name.name,
-                            false,
-                            context.file_no,
-                            Some(call_contract_no),
-                            ns,
-                        ),
-                        false,
-                        context,
-                        resolve_to,
-                        ns,
-                        symtable,
-                        diagnostics,
-                    );
-                } else {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        "function calls via contract name are only valid for base contracts".into(),
-                    ));
-                }
-            }
-        }
-    }
-
-    if let Some(mut path) = ns.expr_to_identifier_path(var) {
-        path.identifiers.push(func_name.clone());
-
-        if let Ok(list) = ns.resolve_free_function_with_namespace(
-            context.file_no,
-            &path,
-            &mut Diagnostics::default(),
-        ) {
-            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,
-                func_name,
-                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() {
-        let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?;
-
-        let mut arguments = HashMap::new();
-
-        // check if the arguments are not garbage
-        for arg in args {
-            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,
-                );
-
-                continue;
-            }
-
-            arguments.insert(arg.name.name.as_str(), &arg.expr);
-        }
-
-        let mut errors = Diagnostics::default();
-        let mut name_matches: Vec<usize> = Vec::new();
-
-        // function call
-        for function_no in ns.contracts[*external_contract_no].all_functions.keys() {
-            if ns.functions[*function_no].name != func_name.name
-                || ns.functions[*function_no].ty != pt::FunctionTy::Function
-            {
-                continue;
-            }
-
-            name_matches.push(*function_no);
-        }
-
-        for function_no in &name_matches {
-            let func = &ns.functions[*function_no];
-
-            let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
-            let params_len = func.params.len();
-
-            let mut matches = true;
-
-            if unnamed_params > 0 {
-                errors.push(Diagnostic::cast_error_with_note(
-                    *loc,
-                    format!(
-                        "function cannot be called with named arguments as {} of its parameters do not have names",
-                        unnamed_params,
-                    ),
-                    func.loc,
-                    format!("definition of {}", func.name),
-                ));
-                matches = false;
-            } else if params_len != args.len() {
-                errors.push(Diagnostic::cast_error(
-                    *loc,
-                    format!(
-                        "function expects {} arguments, {} provided",
-                        params_len,
-                        args.len()
-                    ),
-                ));
-                matches = false;
-            }
-            let mut cast_args = Vec::new();
-
-            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 => {
-                        matches = false;
-                        diagnostics.push(Diagnostic::cast_error(
-                            *loc,
-                            format!(
-                                "missing argument '{}' to function '{}'",
-                                param.name_as_str(),
-                                func_name.name,
-                            ),
-                        ));
-                        continue;
-                    }
-                };
-
-                let arg = match expression(
-                    arg,
-                    context,
-                    ns,
-                    symtable,
-                    &mut errors,
-                    ResolveTo::Type(&param.ty),
-                ) {
-                    Ok(e) => e,
-                    Err(()) => {
-                        matches = false;
-                        continue;
-                    }
-                };
-
-                match arg.cast(&arg.loc(), &param.ty, true, ns, &mut errors) {
-                    Ok(expr) => cast_args.push(expr),
-                    Err(()) => {
-                        matches = false;
-                        break;
-                    }
-                }
-            }
-
-            if matches {
-                if !ns.functions[*function_no].is_public() {
-                    diagnostics.push(Diagnostic::error(
-                        *loc,
-                        format!(
-                            "function '{}' is not 'public' or 'external'",
-                            func_name.name
-                        ),
-                    ));
-                } 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.name
-                            ),
-                        ));
-                    }
-                }
-
-                let func = &ns.functions[*function_no];
-                let returns = function_returns(func, resolve_to);
-                let ty = function_type(func, true, resolve_to);
-
-                return Ok(Expression::ExternalFunctionCall {
-                    loc: *loc,
-                    returns,
-                    function: Box::new(Expression::ExternalFunction {
-                        loc: *loc,
-                        ty,
-                        function_no: *function_no,
-                        address: Box::new(var_expr.cast(
-                            &var.loc(),
-                            &Type::Contract(func.contract_no.unwrap()),
-                            true,
-                            ns,
-                            diagnostics,
-                        )?),
-                    }),
-                    args: cast_args,
-                    call_args,
-                });
-            } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) {
-                return Err(());
-            }
-        }
-
-        match name_matches.len() {
-            0 => {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "contract '{}' does not have function '{}'",
-                        var_ty.deref_any().to_string(ns),
-                        func_name.name
-                    ),
-                ));
-            }
-            1 => diagnostics.extend(errors),
-            _ => {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "cannot find overloaded function which matches signature".to_string(),
-                ));
-            }
-        }
-        return Err(());
-    }
-
-    diagnostics.push(Diagnostic::error(
-        func_name.loc,
-        format!("method '{}' does not exist", func_name.name),
-    ));
-
-    Err(())
-}
-
-// When generating shifts, llvm wants both arguments to have the same width. We want the
-// result of the shift to be left argument, so this function coercies the right argument
-// into the right length.
-pub fn cast_shift_arg(
-    loc: &pt::Loc,
-    expr: Expression,
-    from_width: u16,
-    ty: &Type,
-    ns: &Namespace,
-) -> Expression {
-    let to_width = ty.bits(ns);
-
-    if from_width == to_width {
-        expr
-    } else if from_width < to_width && ty.is_signed_int() {
-        Expression::SignExt {
-            loc: *loc,
-            to: ty.clone(),
-            expr: Box::new(expr),
-        }
-    } else if from_width < to_width && !ty.is_signed_int() {
-        Expression::ZeroExt {
-            loc: *loc,
-            to: ty.clone(),
-            expr: Box::new(expr),
-        }
-    } else {
-        Expression::Trunc {
-            loc: *loc,
-            to: ty.clone(),
-            expr: Box::new(expr),
-        }
-    }
-}
-
-/// Given an parsed literal array, ensure that it is valid. All the elements in the array
-/// must of the same type. The array might be a multidimensional array; all the leaf nodes
-/// must match.
-fn array_literal(
-    loc: &pt::Loc,
-    exprs: &[pt::Expression],
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut dimensions = Vec::new();
-    let mut flattened = Vec::new();
-
-    let resolve_to = match resolve_to {
-        ResolveTo::Type(Type::Array(elem_ty, _)) => ResolveTo::Type(elem_ty),
-        // Solana seeds are a slice of slice of bytes, e.g. [ [ "fo", "o" ], [ "b", "a", "r"]]. In this
-        // case we want to resolve
-        ResolveTo::Type(Type::Slice(slice)) if matches!(slice.as_ref(), Type::Slice(_)) => {
-            let mut res = Vec::new();
-            let mut has_errors = false;
-
-            for expr in exprs {
-                let expr = match expression(
-                    expr,
-                    context,
-                    ns,
-                    symtable,
-                    diagnostics,
-                    ResolveTo::Type(&Type::Array(slice.clone(), vec![ArrayLength::Dynamic])),
-                ) {
-                    Ok(expr) => expr,
-                    Err(_) => {
-                        has_errors = true;
-                        continue;
-                    }
-                };
-
-                let ty = expr.ty();
-
-                if let Type::Array(elem, dims) = &ty {
-                    if elem != slice || dims.len() != 1 {
-                        diagnostics.push(Diagnostic::error(
-                            expr.loc(),
-                            format!(
-                                "type {} found where array {} expected",
-                                elem.to_string(ns),
-                                slice.to_string(ns)
-                            ),
-                        ));
-                        has_errors = true;
-                    }
-                } else {
-                    diagnostics.push(Diagnostic::error(
-                        expr.loc(),
-                        format!(
-                            "type {} found where array of slices expected",
-                            ty.to_string(ns)
-                        ),
-                    ));
-                    has_errors = true;
-                }
-
-                res.push(expr);
-            }
-
-            return if has_errors {
-                Err(())
-            } else {
-                let aty = Type::Array(
-                    slice.clone(),
-                    vec![ArrayLength::Fixed(BigInt::from(exprs.len()))],
-                );
-
-                Ok(Expression::ArrayLiteral {
-                    loc: *loc,
-                    ty: aty,
-                    dimensions: vec![exprs.len() as u32],
-                    values: res,
-                })
-            };
-        }
-        _ => resolve_to,
-    };
-
-    check_subarrays(
-        exprs,
-        &mut Some(&mut dimensions),
-        &mut flattened,
-        diagnostics,
-    )?;
-
-    if flattened.is_empty() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            "array requires at least one element".to_string(),
-        ));
-        return Err(());
-    }
-
-    let mut flattened = flattened.iter();
-
-    // We follow the solidity scheme were everthing gets implicitly converted to the
-    // type of the first element
-    let mut first = expression(
-        flattened.next().unwrap(),
-        context,
-        ns,
-        symtable,
-        diagnostics,
-        resolve_to,
-    )?;
-
-    let ty = if let ResolveTo::Type(ty) = resolve_to {
-        first = first.cast(&first.loc(), ty, true, ns, diagnostics)?;
-
-        ty.clone()
-    } else {
-        first.ty()
-    };
-
-    used_variable(ns, &first, symtable);
-    let mut exprs = vec![first];
-
-    for e in flattened {
-        let mut other = expression(e, context, ns, symtable, diagnostics, ResolveTo::Type(&ty))?;
-        used_variable(ns, &other, symtable);
-
-        if other.ty() != ty {
-            other = other.cast(&e.loc(), &ty, true, ns, diagnostics)?;
-        }
-
-        exprs.push(other);
-    }
-
-    let aty = Type::Array(
-        Box::new(ty),
-        dimensions
-            .iter()
-            .map(|n| ArrayLength::Fixed(BigInt::from_u32(*n).unwrap()))
-            .collect::<Vec<ArrayLength>>(),
-    );
-
-    if context.constant {
-        Ok(Expression::ConstArrayLiteral {
-            loc: *loc,
-            ty: aty,
-            dimensions,
-            values: exprs,
-        })
-    } else {
-        Ok(Expression::ArrayLiteral {
-            loc: *loc,
-            ty: aty,
-            dimensions,
-            values: exprs,
-        })
-    }
-}
-
-/// Traverse the literal looking for sub arrays. Ensure that all the sub
-/// arrays are the same length, and returned a flattened array of elements
-fn check_subarrays<'a>(
-    exprs: &'a [pt::Expression],
-    dims: &mut Option<&mut Vec<u32>>,
-    flatten: &mut Vec<&'a pt::Expression>,
-    diagnostics: &mut Diagnostics,
-) -> Result<(), ()> {
-    if let Some(pt::Expression::ArrayLiteral(_, first)) = exprs.get(0) {
-        // ensure all elements are array literals of the same length
-        check_subarrays(first, dims, flatten, diagnostics)?;
-
-        for (i, e) in exprs.iter().enumerate().skip(1) {
-            if let pt::Expression::ArrayLiteral(_, other) = e {
-                if other.len() != first.len() {
-                    diagnostics.push(Diagnostic::error(
-                        e.loc(),
-                        format!(
-                            "array elements should be identical, sub array {} has {} elements rather than {}", i + 1, other.len(), first.len()
-                        ),
-                    ));
-                    return Err(());
-                }
-                check_subarrays(other, &mut None, flatten, diagnostics)?;
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    e.loc(),
-                    format!("array element {} should also be an array", i + 1),
-                ));
-                return Err(());
-            }
-        }
-    } else {
-        for (i, e) in exprs.iter().enumerate().skip(1) {
-            if let pt::Expression::ArrayLiteral(loc, _) = e {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "array elements should be of the type, element {} is unexpected array",
-                        i + 1
-                    ),
-                ));
-                return Err(());
-            }
-        }
-        flatten.extend(exprs);
-    }
-
-    if let Some(dims) = dims.as_deref_mut() {
-        dims.push(exprs.len() as u32);
-    }
-
-    Ok(())
-}
-
-/// Function call arguments
-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))
-}
-
-/// Parse call arguments for external calls
-fn parse_call_args(
-    loc: &pt::Loc,
-    call_args: &[&pt::NamedArgument],
-    external_call: bool,
-    context: &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,
-                )?));
-            }
-            "address" => {
-                if ns.target != Target::Solana {
-                    diagnostics.push(Diagnostic::error(
-                        arg.loc,
-                        format!(
-                            "'address' not permitted for external calls or constructors on {}",
-                            ns.target
-                        ),
-                    ));
-                    return Err(());
-                }
-
-                if external_call {
-                    diagnostics.push(Diagnostic::error(
-                        arg.loc,
-                        "'address' not valid for external calls".to_string(),
-                    ));
-                    return Err(());
-                }
-
-                let ty = Type::Address(false);
-
-                let expr = expression(
-                    &arg.expr,
-                    context,
-                    ns,
-                    symtable,
-                    diagnostics,
-                    ResolveTo::Type(&ty),
-                )?;
-
-                res.address = 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::Uint(256);
-
-                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(());
-                }
-
-                let expr = expression(
-                    &arg.expr,
-                    context,
-                    ns,
-                    symtable,
-                    diagnostics,
-                    ResolveTo::Unknown,
-                )?;
-
-                let mut correct_ty = false;
-                let expr_ty = expr.ty();
-
-                // if let chains would really help here
-                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(());
-                }
-
-                res.accounts = Some(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(Box::new(Type::Slice(Box::new(Type::Bytes(1)))));
-
-                let expr = expression(
-                    &arg.expr,
-                    context,
-                    ns,
-                    symtable,
-                    diagnostics,
-                    ResolveTo::Type(&ty),
-                )?;
-
-                res.seeds = Some(Box::new(expr));
-            }
-            _ => {
-                diagnostics.push(Diagnostic::error(
-                    arg.loc,
-                    format!("'{}' not a valid call parameter", arg.name.name),
-                ));
-                return Err(());
-            }
-        }
-    }
-
-    // address is required on solana constructors
-    if ns.target == Target::Solana && !external_call && res.address.is_none() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!("'address' call argument required on {}", ns.target),
-        ));
-        return Err(());
-    }
-
-    Ok(res)
-}
-
-pub fn named_call_expr(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::NamedArgument],
-    is_destructible: bool,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
-) -> Result<Expression, ()> {
-    let mut nullsink = Diagnostics::default();
-
-    // is it a struct literal
-    match ns.resolve_type(
-        context.file_no,
-        context.contract_no,
-        true,
-        ty,
-        &mut nullsink,
-    ) {
-        Ok(Type::Struct(str_ty)) => {
-            return named_struct_literal(loc, &str_ty, args, context, ns, symtable, diagnostics);
-        }
-        Ok(_) => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                "struct or function expected".to_string(),
-            ));
-            return Err(());
-        }
-        _ => {}
-    }
-
-    // not a struct literal, remove those errors and try resolving as function call
-    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)
-}
-
-/// Resolve any callable expression
-pub fn call_expr(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::Expression],
-    is_destructible: bool,
-    context: &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,
-        true,
-        ty,
-        &mut nullsink,
-    ) {
-        Ok(Type::Struct(str_ty)) => {
-            return struct_literal(loc, &str_ty, args, context, ns, symtable, diagnostics);
-        }
-        Ok(to) => {
-            // Cast
-            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)
-}
-
-/// Is it an (new C).value(1).gas(2)(1, 2, 3) style constructor (not supported)?
-fn deprecated_constructor_arguments(
-    expr: &pt::Expression,
-    diagnostics: &mut Diagnostics,
-) -> Result<(), ()> {
-    match expr.remove_parenthesis() {
-        pt::Expression::FunctionCall(func_loc, ty, _) => {
-            if let pt::Expression::MemberAccess(_, ty, call_arg) = ty.as_ref() {
-                if deprecated_constructor_arguments(ty, diagnostics).is_err() {
-                    // location should be the identifier and the arguments
-                    let mut loc = call_arg.loc;
-                    if let pt::Loc::File(_, _, end) = &mut loc {
-                        *end = func_loc.end();
-                    }
-                    diagnostics.push(Diagnostic::error(
-                        loc,
-                        format!("deprecated call argument syntax '.{}(...)' is not supported, use '{{{}: ...}}' instead", call_arg.name, call_arg.name)
-                    ));
-                    return Err(());
-                }
-            }
-        }
-        pt::Expression::New(..) => {
-            return Err(());
-        }
-        _ => (),
-    }
-
-    Ok(())
-}
-
-/// Resolve function call
-pub fn function_call_expr(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::Expression],
-    context: &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) => {
-            if context.constant {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "cannot call function in constant expression".to_string(),
-                ));
-                return Err(());
-            }
-
-            method_call_pos_args(
-                loc,
-                member,
-                func,
-                args,
-                &call_args,
-                call_args_loc,
-                context,
-                ns,
-                symtable,
-                diagnostics,
-                resolve_to,
-            )
-        }
-        pt::Expression::Variable(id) => {
-            // is it a builtin
-            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 context.constant {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    "cannot call function in constant expression".to_string(),
-                ));
-                return Err(());
-            }
-
-            // is there a local variable or contract variable with this name
-            if symtable.find(&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(());
-                }
-
-                function_call_pos_args(
-                    loc,
-                    id,
-                    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,
-        ),
-    }
-}
-
-/// Resolve function call expression with named arguments
-pub fn named_function_call_expr(
-    loc: &pt::Loc,
-    ty: &pt::Expression,
-    args: &[pt::NamedArgument],
-    context: &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(());
-            }
-
-            function_call_named_args(
-                loc,
-                id,
-                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(())
-        }
-    }
-}
-
-/// Get the return values for a function call
-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]
-    }
-}
-
-/// Get the function type for an internal.external function call
-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,
-        }
-    }
-}
-
-/// Calculate storage subscript
-fn mapping_subscript(
-    loc: &pt::Loc,
-    mapping: Expression,
-    index: &pt::Expression,
-    context: &ExprContext,
-    ns: &mut Namespace,
-    symtable: &mut Symtable,
-    diagnostics: &mut Diagnostics,
-) -> Result<Expression, ()> {
-    let ty = mapping.ty();
-    let elem_ty = ty.storage_array_elem();
-
-    if let Type::Mapping(key_ty, _) = ty.deref_any() {
-        let index_expr = expression(
-            index,
-            context,
-            ns,
-            symtable,
-            diagnostics,
-            ResolveTo::Type(key_ty),
-        )?
-        .cast(&index.loc(), key_ty, true, ns, diagnostics)?;
-
-        Ok(Expression::Subscript {
-            loc: *loc,
-            ty: elem_ty,
-            array_ty: ty,
-            array: Box::new(mapping),
-            index: Box::new(index_expr),
-        })
-    } else {
-        unreachable!()
-    }
-}

+ 865 - 0
src/sema/expression/arithmetic.rs

@@ -0,0 +1,865 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, Namespace, RetrieveType, StringLocation, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::eval::eval_const_rational;
+use crate::sema::expression::integers::{coerce, coerce_number, get_int_length};
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::{check_var_usage_expression, used_variable};
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+
+pub(super) fn subtract(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    if ty.is_rational() {
+        let expr = Expression::Subtract {
+            loc: *loc,
+            ty,
+            unchecked: false,
+            left: Box::new(left),
+            right: Box::new(right),
+        };
+
+        return match eval_const_rational(&expr, ns) {
+            Ok(_) => Ok(expr),
+            Err(diag) => {
+                diagnostics.push(diag);
+                Err(())
+            }
+        };
+    }
+
+    Ok(Expression::Subtract {
+        loc: *loc,
+        ty: ty.clone(),
+        unchecked: context.unchecked,
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn bitwise_or(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        true,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::BitwiseOr {
+        loc: *loc,
+        ty: ty.clone(),
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn bitwise_and(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        true,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::BitwiseAnd {
+        loc: *loc,
+        ty: ty.clone(),
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn bitwise_xor(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        true,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::BitwiseXor {
+        loc: *loc,
+        ty: ty.clone(),
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn shift_left(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+    // left hand side may be bytes/int/uint
+    // right hand size may be int/uint
+    let _ = get_int_length(&left.ty(), &l.loc(), true, ns, diagnostics)?;
+    let (right_length, _) = get_int_length(&right.ty(), &r.loc(), false, ns, diagnostics)?;
+
+    let left_type = left.ty().deref_any().clone();
+
+    Ok(Expression::ShiftLeft {
+        loc: *loc,
+        ty: left_type.clone(),
+        left: Box::new(left.cast(loc, &left_type, true, ns, diagnostics)?),
+        right: Box::new(cast_shift_arg(loc, right, right_length, &left_type, ns)),
+    })
+}
+
+pub(super) fn shift_right(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let left_type = left.ty().deref_any().clone();
+    // left hand side may be bytes/int/uint
+    // right hand size may be int/uint
+    let _ = get_int_length(&left_type, &l.loc(), true, ns, diagnostics)?;
+    let (right_length, _) = get_int_length(&right.ty(), &r.loc(), false, ns, diagnostics)?;
+
+    Ok(Expression::ShiftRight {
+        loc: *loc,
+        ty: left_type.clone(),
+        left: Box::new(left.cast(loc, &left_type, true, ns, diagnostics)?),
+        right: Box::new(cast_shift_arg(loc, right, right_length, &left_type, ns)),
+        sign: left_type.is_signed_int(),
+    })
+}
+
+pub(super) fn multiply(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    if ty.is_rational() {
+        let expr = Expression::Multiply {
+            loc: *loc,
+            ty,
+            unchecked: false,
+            left: Box::new(left),
+            right: Box::new(right),
+        };
+
+        return match eval_const_rational(&expr, ns) {
+            Ok(_) => Ok(expr),
+            Err(diag) => {
+                diagnostics.push(diag);
+                Err(())
+            }
+        };
+    }
+
+    // If we don't know what type the result is going to be, make any possible result fit.
+    if resolve_to == ResolveTo::Unknown {
+        let bits = std::cmp::min(256, ty.bits(ns) * 2);
+
+        if ty.is_signed_int() {
+            multiply(
+                loc,
+                l,
+                r,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&Type::Int(bits)),
+            )
+        } else {
+            multiply(
+                loc,
+                l,
+                r,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&Type::Uint(bits)),
+            )
+        }
+    } else {
+        Ok(Expression::Multiply {
+            loc: *loc,
+            ty: ty.clone(),
+            unchecked: context.unchecked,
+            left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+            right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+        })
+    }
+}
+
+pub(super) fn divide(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::Divide {
+        loc: *loc,
+        ty: ty.clone(),
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn modulo(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    let ty = coerce_number(
+        &left.ty(),
+        &l.loc(),
+        &right.ty(),
+        &r.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::Modulo {
+        loc: *loc,
+        ty: ty.clone(),
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+pub(super) fn power(
+    loc: &pt::Loc,
+    b: &pt::Expression,
+    e: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut base = expression(b, context, ns, symtable, diagnostics, resolve_to)?;
+
+    // If we don't know what type the result is going to be, assume
+    // the result is 256 bits
+    if resolve_to == ResolveTo::Unknown {
+        if base.ty().is_signed_int() {
+            base = expression(
+                b,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&Type::Int(256)),
+            )?;
+        } else {
+            base = expression(
+                b,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&Type::Uint(256)),
+            )?;
+        };
+    }
+
+    let exp = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+
+    check_var_usage_expression(ns, &base, &exp, symtable);
+
+    let base_type = base.ty();
+    let exp_type = exp.ty();
+
+    // solc-0.5.13 does not allow either base or exp to be signed
+    if base_type.is_signed_int() || exp_type.is_signed_int() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            "exponation (**) is not allowed with signed types".to_string(),
+        ));
+        return Err(());
+    }
+
+    let ty = coerce_number(
+        &base_type,
+        &b.loc(),
+        &exp_type,
+        &e.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    Ok(Expression::Power {
+        loc: *loc,
+        ty: ty.clone(),
+        unchecked: context.unchecked,
+        base: Box::new(base.cast(&b.loc(), &ty, true, ns, diagnostics)?),
+        exp: Box::new(exp.cast(&e.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+/// Test for equality; first check string equality, then integer equality
+pub(super) fn equal(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+    let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    // Comparing stringliteral against stringliteral
+    if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
+        (&left, &right)
+    {
+        return Ok(Expression::BoolLiteral {
+            loc: *loc,
+            value: l == r,
+        });
+    }
+
+    let left_type = left.ty();
+    let right_type = right.ty();
+
+    // compare string against literal
+    match (&left, &right_type.deref_any()) {
+        (Expression::BytesLiteral { value: l, .. }, Type::String)
+        | (Expression::BytesLiteral { value: l, .. }, Type::DynamicBytes) => {
+            return Ok(Expression::StringCompare {
+                loc: *loc,
+                left: StringLocation::RunTime(Box::new(right.cast(
+                    &r.loc(),
+                    right_type.deref_any(),
+                    true,
+                    ns,
+                    diagnostics,
+                )?)),
+                right: StringLocation::CompileTime(l.clone()),
+            });
+        }
+        _ => {}
+    }
+
+    match (&right, &left_type.deref_any()) {
+        (Expression::BytesLiteral { value, .. }, Type::String)
+        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
+            return Ok(Expression::StringCompare {
+                loc: *loc,
+                left: StringLocation::RunTime(Box::new(left.cast(
+                    &l.loc(),
+                    left_type.deref_any(),
+                    true,
+                    ns,
+                    diagnostics,
+                )?)),
+                right: StringLocation::CompileTime(value.clone()),
+            });
+        }
+        _ => {}
+    }
+
+    // compare string
+    match (&left_type.deref_any(), &right_type.deref_any()) {
+        (Type::String, Type::String) | (Type::DynamicBytes, Type::DynamicBytes) => {
+            return Ok(Expression::StringCompare {
+                loc: *loc,
+                left: StringLocation::RunTime(Box::new(left.cast(
+                    &l.loc(),
+                    left_type.deref_any(),
+                    true,
+                    ns,
+                    diagnostics,
+                )?)),
+                right: StringLocation::RunTime(Box::new(right.cast(
+                    &r.loc(),
+                    right_type.deref_any(),
+                    true,
+                    ns,
+                    diagnostics,
+                )?)),
+            });
+        }
+        _ => {}
+    }
+
+    let ty = coerce(&left_type, &l.loc(), &right_type, &r.loc(), ns, diagnostics)?;
+
+    let expr = Expression::Equal {
+        loc: *loc,
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    };
+
+    if ty.is_rational() {
+        if let Err(diag) = eval_const_rational(&expr, ns) {
+            diagnostics.push(diag);
+        }
+    }
+
+    Ok(expr)
+}
+
+/// Try string concatenation
+pub(super) fn addition(
+    loc: &pt::Loc,
+    l: &pt::Expression,
+    r: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+    let mut right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+    check_var_usage_expression(ns, &left, &right, symtable);
+
+    // Concatenate stringliteral with stringliteral
+    if let (Expression::BytesLiteral { value: l, .. }, Expression::BytesLiteral { value: r, .. }) =
+        (&left, &right)
+    {
+        let mut c = Vec::with_capacity(l.len() + r.len());
+        c.extend_from_slice(l);
+        c.extend_from_slice(r);
+        let length = c.len();
+        return Ok(Expression::BytesLiteral {
+            loc: *loc,
+            ty: Type::Bytes(length as u8),
+            value: c,
+        });
+    }
+
+    let left_type = left.ty();
+    let right_type = right.ty();
+
+    // compare string against literal
+    match (&left, &right_type) {
+        (Expression::BytesLiteral { value, .. }, Type::String)
+        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
+            return Ok(Expression::StringConcat {
+                loc: *loc,
+                ty: right_type,
+                left: StringLocation::CompileTime(value.clone()),
+                right: StringLocation::RunTime(Box::new(right)),
+            });
+        }
+        _ => {}
+    }
+
+    match (&right, &left_type) {
+        (Expression::BytesLiteral { value, .. }, Type::String)
+        | (Expression::BytesLiteral { value, .. }, Type::DynamicBytes) => {
+            return Ok(Expression::StringConcat {
+                loc: *loc,
+                ty: left_type,
+                left: StringLocation::RunTime(Box::new(left)),
+                right: StringLocation::CompileTime(value.clone()),
+            });
+        }
+        _ => {}
+    }
+
+    // compare string
+    match (&left_type, &right_type) {
+        (Type::String, Type::String) | (Type::DynamicBytes, Type::DynamicBytes) => {
+            return Ok(Expression::StringConcat {
+                loc: *loc,
+                ty: right_type,
+                left: StringLocation::RunTime(Box::new(left)),
+                right: StringLocation::RunTime(Box::new(right)),
+            });
+        }
+        _ => {}
+    }
+
+    let ty = coerce_number(
+        &left_type,
+        &l.loc(),
+        &right_type,
+        &r.loc(),
+        false,
+        false,
+        ns,
+        diagnostics,
+    )?;
+
+    if ty.is_rational() {
+        let expr = Expression::Add {
+            loc: *loc,
+            ty,
+            unchecked: false,
+            left: Box::new(left),
+            right: Box::new(right),
+        };
+
+        return match eval_const_rational(&expr, ns) {
+            Ok(_) => Ok(expr),
+            Err(diag) => {
+                diagnostics.push(diag);
+                Err(())
+            }
+        };
+    }
+
+    // If we don't know what type the result is going to be
+    if resolve_to == ResolveTo::Unknown {
+        let bits = std::cmp::min(256, ty.bits(ns) * 2);
+        let resolve_to = if ty.is_signed_int() {
+            Type::Int(bits)
+        } else {
+            Type::Uint(bits)
+        };
+
+        left = expression(
+            l,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            ResolveTo::Type(&resolve_to),
+        )?;
+        right = expression(
+            r,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            ResolveTo::Type(&resolve_to),
+        )?;
+    }
+
+    Ok(Expression::Add {
+        loc: *loc,
+        ty: ty.clone(),
+        unchecked: context.unchecked,
+        left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+        right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+    })
+}
+
+/// Resolve an increment/decrement with an operator
+pub(super) fn incr_decr(
+    v: &pt::Expression,
+    expr: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let op = |e: Expression, ty: Type| -> Expression {
+        match expr {
+            pt::Expression::PreIncrement(loc, _) => Expression::PreIncrement {
+                loc: *loc,
+                ty,
+                unchecked: context.unchecked,
+                expr: Box::new(e),
+            },
+            pt::Expression::PreDecrement(loc, _) => Expression::PreDecrement {
+                loc: *loc,
+                ty,
+                unchecked: context.unchecked,
+                expr: Box::new(e),
+            },
+            pt::Expression::PostIncrement(loc, _) => Expression::PostIncrement {
+                loc: *loc,
+                ty,
+                unchecked: context.unchecked,
+                expr: Box::new(e),
+            },
+            pt::Expression::PostDecrement(loc, _) => Expression::PostDecrement {
+                loc: *loc,
+                ty,
+                unchecked: context.unchecked,
+                expr: Box::new(e),
+            },
+            _ => unreachable!(),
+        }
+    };
+
+    let mut context = context.clone();
+
+    context.lvalue = true;
+
+    let var = expression(v, &context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
+    used_variable(ns, &var, symtable);
+    let var_ty = var.ty();
+
+    match &var {
+        Expression::ConstantVariable {
+            loc,
+            contract_no: Some(contract_no),
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "cannot assign to constant '{}'",
+                    ns.contracts[*contract_no].variables[*var_no].name
+                ),
+            ));
+            Err(())
+        }
+        Expression::ConstantVariable {
+            loc,
+            contract_no: None,
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
+            ));
+            Err(())
+        }
+        Expression::Variable { ty, var_no, .. } => {
+            match ty {
+                Type::Int(_) | Type::Uint(_) => (),
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        var.loc(),
+                        format!(
+                            "variable '{}' of incorrect type {}",
+                            symtable.get_name(*var_no),
+                            var_ty.to_string(ns)
+                        ),
+                    ));
+                    return Err(());
+                }
+            };
+            Ok(op(var.clone(), ty.clone()))
+        }
+        _ => match &var_ty {
+            Type::Ref(r_ty) => match r_ty.as_ref() {
+                Type::Int(_) | Type::Uint(_) => Ok(op(var, r_ty.as_ref().clone())),
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        var.loc(),
+                        format!("assigning to incorrect type {}", r_ty.to_string(ns)),
+                    ));
+                    Err(())
+                }
+            },
+            Type::StorageRef(immutable, r_ty) => {
+                if *immutable {
+                    if let Some(function_no) = context.function_no {
+                        if !ns.functions[function_no].is_constructor() {
+                            diagnostics.push(Diagnostic::error(
+                                var.loc(),
+                                "cannot assign to immutable outside of constructor".to_string(),
+                            ));
+                            return Err(());
+                        }
+                    }
+                }
+                match r_ty.as_ref() {
+                    Type::Int(_) | Type::Uint(_) => Ok(op(var, r_ty.as_ref().clone())),
+                    _ => {
+                        diagnostics.push(Diagnostic::error(
+                            var.loc(),
+                            format!("assigning to incorrect type {}", r_ty.to_string(ns)),
+                        ));
+                        Err(())
+                    }
+                }
+            }
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    var.loc(),
+                    "expression is not modifiable".to_string(),
+                ));
+                Err(())
+            }
+        },
+    }
+}
+
+// When generating shifts, llvm wants both arguments to have the same width. We want the
+// result of the shift to be left argument, so this function coercies the right argument
+// into the right length.
+pub fn cast_shift_arg(
+    loc: &pt::Loc,
+    expr: Expression,
+    from_width: u16,
+    ty: &Type,
+    ns: &Namespace,
+) -> Expression {
+    let to_width = ty.bits(ns);
+
+    if from_width == to_width {
+        expr
+    } else if from_width < to_width && ty.is_signed_int() {
+        Expression::SignExt {
+            loc: *loc,
+            to: ty.clone(),
+            expr: Box::new(expr),
+        }
+    } else if from_width < to_width && !ty.is_signed_int() {
+        Expression::ZeroExt {
+            loc: *loc,
+            to: ty.clone(),
+            expr: Box::new(expr),
+        }
+    } else {
+        Expression::Trunc {
+            loc: *loc,
+            to: ty.clone(),
+            expr: Box::new(expr),
+        }
+    }
+}

+ 413 - 0
src/sema/expression/assign.rs

@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, Namespace, RetrieveType, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::eval::check_term_for_constant_overflow;
+use crate::sema::expression::integers::get_int_length;
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::{assigned_variable, used_variable};
+use crate::sema::Recurse;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+
+/// Resolve an assignment
+pub(super) fn assign_single(
+    loc: &pt::Loc,
+    left: &pt::Expression,
+    right: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let mut lcontext = context.clone();
+    lcontext.lvalue = true;
+
+    let var = expression(
+        left,
+        &lcontext,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Unknown,
+    )?;
+    assigned_variable(ns, &var, symtable);
+
+    let var_ty = var.ty();
+    let val = expression(
+        right,
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Type(var_ty.deref_any()),
+    )?;
+
+    val.recurse(ns, check_term_for_constant_overflow);
+
+    used_variable(ns, &val, symtable);
+    match &var {
+        Expression::ConstantVariable {
+            loc,
+            contract_no: Some(contract_no),
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "cannot assign to constant '{}'",
+                    ns.contracts[*contract_no].variables[*var_no].name
+                ),
+            ));
+            Err(())
+        }
+        Expression::ConstantVariable {
+            loc,
+            contract_no: None,
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
+            ));
+            Err(())
+        }
+        Expression::StorageVariable {
+            loc,
+            ty,
+            contract_no: var_contract_no,
+            var_no,
+        } => {
+            let store_var = &ns.contracts[*var_contract_no].variables[*var_no];
+
+            if store_var.immutable {
+                if let Some(function_no) = context.function_no {
+                    if !ns.functions[function_no].is_constructor() {
+                        diagnostics.push(Diagnostic::error(
+                            *loc,
+                            format!(
+                                "cannot assign to immutable '{}' outside of constructor",
+                                store_var.name
+                            ),
+                        ));
+                        return Err(());
+                    }
+                }
+            }
+
+            let ty = ty.deref_any();
+
+            Ok(Expression::Assign {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(var.clone()),
+                right: Box::new(val.cast(&right.loc(), ty, true, ns, diagnostics)?),
+            })
+        }
+        Expression::Variable { ty: var_ty, .. } => Ok(Expression::Assign {
+            loc: *loc,
+            ty: var_ty.clone(),
+            left: Box::new(var.clone()),
+            right: Box::new(val.cast(&right.loc(), var_ty, true, ns, diagnostics)?),
+        }),
+        _ => match &var_ty {
+            Type::Ref(r_ty) => Ok(Expression::Assign {
+                loc: *loc,
+                ty: *r_ty.clone(),
+                left: Box::new(var),
+                right: Box::new(val.cast(&right.loc(), r_ty, true, ns, diagnostics)?),
+            }),
+            Type::StorageRef(immutable, r_ty) => {
+                if *immutable {
+                    if let Some(function_no) = context.function_no {
+                        if !ns.functions[function_no].is_constructor() {
+                            diagnostics.push(Diagnostic::error(
+                                *loc,
+                                "cannot assign to immutable outside of constructor".to_string(),
+                            ));
+                            return Err(());
+                        }
+                    }
+                }
+
+                Ok(Expression::Assign {
+                    loc: *loc,
+                    ty: *r_ty.clone(),
+                    left: Box::new(var),
+                    right: Box::new(val.cast(&right.loc(), r_ty, true, ns, diagnostics)?),
+                })
+            }
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    var.loc(),
+                    "expression is not assignable".to_string(),
+                ));
+                Err(())
+            }
+        },
+    }
+}
+
+/// Resolve an assignment with an operator
+pub(super) fn assign_expr(
+    loc: &pt::Loc,
+    left: &pt::Expression,
+    expr: &pt::Expression,
+    right: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let mut lcontext = context.clone();
+    lcontext.lvalue = true;
+
+    let var = expression(
+        left,
+        &lcontext,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Unknown,
+    )?;
+    assigned_variable(ns, &var, symtable);
+    let var_ty = var.ty();
+
+    let resolve_to = if matches!(
+        expr,
+        pt::Expression::AssignShiftLeft(..) | pt::Expression::AssignShiftRight(..)
+    ) {
+        ResolveTo::Unknown
+    } else {
+        ResolveTo::Type(var_ty.deref_any())
+    };
+
+    let set = expression(right, context, ns, symtable, diagnostics, resolve_to)?;
+    used_variable(ns, &set, symtable);
+    let set_type = set.ty();
+
+    let assign_operation = |assign: Expression,
+                            ty: &Type,
+                            ns: &Namespace,
+                            diagnostics: &mut Diagnostics|
+     -> Result<Expression, ()> {
+        let set = match expr {
+            pt::Expression::AssignShiftLeft(..) | pt::Expression::AssignShiftRight(..) => {
+                let left_length = get_int_length(ty, loc, true, ns, diagnostics)?;
+                let right_length = get_int_length(&set_type, &left.loc(), false, ns, diagnostics)?;
+
+                // TODO: does shifting by negative value need compiletime/runtime check?
+                if left_length == right_length {
+                    set
+                } else if right_length < left_length && set_type.is_signed_int() {
+                    Expression::SignExt {
+                        loc: *loc,
+                        to: ty.clone(),
+                        expr: Box::new(set),
+                    }
+                } else if right_length < left_length && !set_type.is_signed_int() {
+                    Expression::ZeroExt {
+                        loc: *loc,
+                        to: ty.clone(),
+                        expr: Box::new(set),
+                    }
+                } else {
+                    Expression::Trunc {
+                        loc: *loc,
+                        to: ty.clone(),
+                        expr: Box::new(set),
+                    }
+                }
+            }
+            _ => set.cast(&right.loc(), ty, true, ns, diagnostics)?,
+        };
+
+        Ok(match expr {
+            pt::Expression::AssignAdd(..) => Expression::Add {
+                loc: *loc,
+                ty: ty.clone(),
+                unchecked: context.unchecked,
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignSubtract(..) => Expression::Subtract {
+                loc: *loc,
+                ty: ty.clone(),
+                unchecked: context.unchecked,
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignMultiply(..) => Expression::Multiply {
+                loc: *loc,
+                ty: ty.clone(),
+                unchecked: context.unchecked,
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignOr(..) => Expression::BitwiseOr {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignAnd(..) => Expression::BitwiseAnd {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignXor(..) => Expression::BitwiseXor {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignShiftLeft(..) => Expression::ShiftLeft {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignShiftRight(..) => Expression::ShiftRight {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+                sign: ty.is_signed_int(),
+            },
+            pt::Expression::AssignDivide(..) => Expression::Divide {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            pt::Expression::AssignModulo(..) => Expression::Modulo {
+                loc: *loc,
+                ty: ty.clone(),
+                left: Box::new(assign),
+                right: Box::new(set),
+            },
+            _ => unreachable!(),
+        })
+    };
+
+    match &var {
+        Expression::ConstantVariable {
+            loc,
+            contract_no: Some(contract_no),
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "cannot assign to constant '{}'",
+                    ns.contracts[*contract_no].variables[*var_no].name
+                ),
+            ));
+            Err(())
+        }
+        Expression::ConstantVariable {
+            loc,
+            contract_no: None,
+            var_no,
+            ..
+        } => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("cannot assign to constant '{}'", ns.constants[*var_no].name),
+            ));
+            Err(())
+        }
+        Expression::Variable { var_no, .. } => {
+            match var_ty {
+                Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => (),
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        var.loc(),
+                        format!(
+                            "variable '{}' of incorrect type {}",
+                            symtable.get_name(*var_no),
+                            var_ty.to_string(ns)
+                        ),
+                    ));
+                    return Err(());
+                }
+            };
+            Ok(Expression::Assign {
+                loc: *loc,
+                ty: var_ty.clone(),
+                left: Box::new(var.clone()),
+                right: Box::new(assign_operation(var, &var_ty, ns, diagnostics)?),
+            })
+        }
+        _ => match &var_ty {
+            Type::Ref(r_ty) => match r_ty.as_ref() {
+                Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => Ok(Expression::Assign {
+                    loc: *loc,
+                    ty: *r_ty.clone(),
+                    left: Box::new(var.clone()),
+                    right: Box::new(assign_operation(
+                        var.cast(loc, r_ty, true, ns, diagnostics)?,
+                        r_ty,
+                        ns,
+                        diagnostics,
+                    )?),
+                }),
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        var.loc(),
+                        format!("assigning to incorrect type {}", r_ty.to_string(ns)),
+                    ));
+                    Err(())
+                }
+            },
+            Type::StorageRef(immutable, r_ty) => {
+                if *immutable {
+                    if let Some(function_no) = context.function_no {
+                        if !ns.functions[function_no].is_constructor() {
+                            diagnostics.push(Diagnostic::error(
+                                *loc,
+                                "cannot assign to immutable outside of constructor".to_string(),
+                            ));
+                            return Err(());
+                        }
+                    }
+                }
+
+                match r_ty.as_ref() {
+                    Type::Bytes(_) | Type::Int(_) | Type::Uint(_) => Ok(Expression::Assign {
+                        loc: *loc,
+                        ty: *r_ty.clone(),
+                        left: Box::new(var.clone()),
+                        right: Box::new(assign_operation(
+                            var.cast(loc, r_ty, true, ns, diagnostics)?,
+                            r_ty,
+                            ns,
+                            diagnostics,
+                        )?),
+                    }),
+                    _ => {
+                        diagnostics.push(Diagnostic::error(
+                            var.loc(),
+                            format!("assigning to incorrect type {}", r_ty.to_string(ns)),
+                        ));
+                        Err(())
+                    }
+                }
+            }
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    var.loc(),
+                    "expression is not assignable".to_string(),
+                ));
+                Err(())
+            }
+        },
+    }
+}

+ 583 - 0
src/sema/expression/constructor.rs

@@ -0,0 +1,583 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{ArrayLength, CallArgs, Expression, Namespace, RetrieveType, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::function_call::{collect_call_args, parse_call_args};
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::used_variable;
+use crate::Target;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+use std::collections::BTreeMap;
+
+/// Resolve an new contract expression with positional arguments
+fn constructor(
+    loc: &pt::Loc,
+    no: usize,
+    args: &[pt::Expression],
+    call_args: CallArgs,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    // The current contract cannot be constructed with new. In order to create
+    // the contract, we need the code hash of the contract. Part of that code
+    // will be code we're emitted here. So we end up with a crypto puzzle.
+    let context_contract_no = match context.contract_no {
+        Some(n) if n == no => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "new cannot construct current contract '{}'",
+                    ns.contracts[no].name
+                ),
+            ));
+            return Err(());
+        }
+        Some(n) => n,
+        None => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "new contract not allowed in this context".to_string(),
+            ));
+            return Err(());
+        }
+    };
+
+    if !ns.contracts[no].instantiable {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "cannot construct '{}' of type '{}'",
+                ns.contracts[no].name, ns.contracts[no].ty
+            ),
+        ));
+
+        return Err(());
+    }
+
+    if ns.target == Target::Solana && ns.contracts[no].program_id.is_none() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "in order to instantiate contract '{}', a @program_id is required on contract '{}'",
+                ns.contracts[no].name, ns.contracts[no].name
+            ),
+        ));
+    }
+
+    // check for circular references
+    if circular_reference(no, context_contract_no, ns) {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "circular reference creating contract '{}'",
+                ns.contracts[no].name
+            ),
+        ));
+        return Err(());
+    }
+
+    if !ns.contracts[context_contract_no].creates.contains(&no) {
+        ns.contracts[context_contract_no].creates.push(no);
+    }
+
+    match match_constructor_to_args(loc, args, no, context, ns, symtable, diagnostics) {
+        Ok((constructor_no, cast_args)) => Ok(Expression::Constructor {
+            loc: *loc,
+            contract_no: no,
+            constructor_no,
+            args: cast_args,
+            call_args,
+        }),
+        Err(()) => Err(()),
+    }
+}
+
+/// Try and find constructor for arguments
+pub fn match_constructor_to_args(
+    loc: &pt::Loc,
+    args: &[pt::Expression],
+    contract_no: usize,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<(Option<usize>, Vec<Expression>), ()> {
+    let mut errors = Diagnostics::default();
+
+    // constructor call
+    let function_nos: Vec<usize> = ns.contracts[contract_no]
+        .functions
+        .iter()
+        .filter(|function_no| ns.functions[**function_no].is_constructor())
+        .copied()
+        .collect();
+
+    for function_no in &function_nos {
+        let mut matches = true;
+
+        let params_len = ns.functions[*function_no].params.len();
+
+        if params_len != args.len() {
+            errors.push(Diagnostic::cast_error(
+                *loc,
+                format!(
+                    "constructor expects {} arguments, {} provided",
+                    params_len,
+                    args.len()
+                ),
+            ));
+            matches = false;
+        }
+
+        let mut cast_args = Vec::new();
+
+        // resolve arguments for this constructor
+        for (i, arg) in args.iter().enumerate() {
+            let ty = ns.functions[*function_no]
+                .params
+                .get(i)
+                .map(|p| p.ty.clone());
+
+            let arg = match expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                &mut errors,
+                if let Some(ty) = &ty {
+                    ResolveTo::Type(ty)
+                } else {
+                    ResolveTo::Unknown
+                },
+            ) {
+                Ok(v) => v,
+                Err(()) => {
+                    matches = false;
+                    continue;
+                }
+            };
+
+            if let Some(ty) = &ty {
+                match arg.cast(&arg.loc(), ty, true, ns, &mut errors) {
+                    Ok(expr) => cast_args.push(expr),
+                    Err(()) => {
+                        matches = false;
+                    }
+                }
+            }
+        }
+
+        if matches {
+            return Ok((Some(*function_no), cast_args));
+        } else if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
+            return Err(());
+        }
+    }
+
+    match function_nos.len() {
+        0 if args.is_empty() => {
+            return Ok((None, Vec::new()));
+        }
+        0 | 1 => {
+            diagnostics.extend(errors);
+        }
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "cannot find overloaded constructor which matches signature".to_string(),
+            ));
+        }
+    }
+
+    Err(())
+}
+
+/// check if from creates to, recursively
+pub(super) fn circular_reference(from: usize, to: usize, ns: &Namespace) -> bool {
+    if ns.contracts[from].creates.contains(&to) {
+        return true;
+    }
+
+    ns.contracts[from]
+        .creates
+        .iter()
+        .any(|n| circular_reference(*n, to, ns))
+}
+
+/// Resolve an new contract expression with named arguments
+pub fn constructor_named_args(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::NamedArgument],
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let (ty, call_args, _) = collect_call_args(ty, diagnostics)?;
+
+    let call_args = parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?;
+
+    let no = match ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)? {
+        Type::Contract(n) => n,
+        _ => {
+            diagnostics.push(Diagnostic::error(*loc, "contract expected".to_string()));
+            return Err(());
+        }
+    };
+
+    // The current contract cannot be constructed with new. In order to create
+    // the contract, we need the code hash of the contract. Part of that code
+    // will be code we're emitted here. So we end up with a crypto puzzle.
+    let context_contract_no = match context.contract_no {
+        Some(n) if n == no => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "new cannot construct current contract '{}'",
+                    ns.contracts[no].name
+                ),
+            ));
+            return Err(());
+        }
+        Some(n) => n,
+        None => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "new contract not allowed in this context".to_string(),
+            ));
+            return Err(());
+        }
+    };
+
+    if !ns.contracts[no].instantiable {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "cannot construct '{}' of type '{}'",
+                ns.contracts[no].name, ns.contracts[no].ty
+            ),
+        ));
+
+        return Err(());
+    }
+
+    // check for circular references
+    if circular_reference(no, context_contract_no, ns) {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "circular reference creating contract '{}'",
+                ns.contracts[no].name
+            ),
+        ));
+        return Err(());
+    }
+
+    if !ns.contracts[context_contract_no].creates.contains(&no) {
+        ns.contracts[context_contract_no].creates.push(no);
+    }
+
+    let mut arguments: BTreeMap<&str, &pt::Expression> = BTreeMap::new();
+
+    for arg in args {
+        if let Some(prev) = arguments.get(arg.name.name.as_str()) {
+            diagnostics.push(Diagnostic::error_with_note(
+                *loc,
+                format!("duplicate argument name '{}'", arg.name.name),
+                prev.loc(),
+                String::from("location of previous argument"),
+            ));
+            return Err(());
+        }
+        arguments.insert(&arg.name.name, &arg.expr);
+    }
+
+    let mut errors = Diagnostics::default();
+
+    // constructor call
+    let function_nos: Vec<usize> = ns.contracts[no]
+        .functions
+        .iter()
+        .filter(|function_no| ns.functions[**function_no].is_constructor())
+        .copied()
+        .collect();
+
+    // constructor call
+    for function_no in &function_nos {
+        let func = &ns.functions[*function_no];
+        let params_len = func.params.len();
+
+        let mut matches = true;
+
+        let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
+
+        if unnamed_params > 0 {
+            errors.push(Diagnostic::cast_error_with_note(
+                *loc,
+                format!(
+                    "constructor cannot be called with named arguments as {} of its parameters do not have names",
+                    unnamed_params,
+                ),
+                func.loc,
+                format!("definition of {}", func.ty),
+            ));
+            matches = false;
+        } else if params_len != args.len() {
+            errors.push(Diagnostic::cast_error_with_note(
+                *loc,
+                format!(
+                    "constructor expects {} arguments, {} provided",
+                    params_len,
+                    args.len()
+                ),
+                func.loc,
+                "definition of constructor".to_owned(),
+            ));
+            matches = false;
+        }
+
+        let mut cast_args = Vec::new();
+
+        let func_loc = ns.functions[*function_no].loc;
+
+        // check if arguments can be implicitly casted
+        for i in 0..params_len {
+            let param = ns.functions[*function_no].params[i].clone();
+
+            let arg = match arguments.get(param.name_as_str()) {
+                Some(a) => a,
+                None => {
+                    matches = false;
+                    errors.push(Diagnostic::cast_error_with_note(
+                        *loc,
+                        format!("missing argument '{}' to constructor", param.name_as_str()),
+                        func_loc,
+                        "definition of constructor".to_owned(),
+                    ));
+                    break;
+                }
+            };
+
+            let arg = match expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                &mut errors,
+                ResolveTo::Type(&param.ty),
+            ) {
+                Ok(e) => e,
+                Err(()) => {
+                    matches = false;
+                    continue;
+                }
+            };
+
+            match arg.cast(&arg.loc(), &param.ty, true, ns, &mut errors) {
+                Ok(expr) => cast_args.push(expr),
+                Err(()) => {
+                    matches = false;
+                }
+            }
+        }
+
+        if matches {
+            return Ok(Expression::Constructor {
+                loc: *loc,
+                contract_no: no,
+                constructor_no: Some(*function_no),
+                args: cast_args,
+                call_args,
+            });
+        } else if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
+            return Err(());
+        }
+    }
+
+    match function_nos.len() {
+        0 if args.is_empty() => Ok(Expression::Constructor {
+            loc: *loc,
+            contract_no: no,
+            constructor_no: None,
+            args: Vec::new(),
+            call_args,
+        }),
+        0 | 1 => {
+            diagnostics.extend(errors);
+
+            Err(())
+        }
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "cannot find overloaded constructor which matches signature".to_string(),
+            ));
+
+            Err(())
+        }
+    }
+}
+
+/// Resolve an new expression
+pub fn new(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::Expression],
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
+
+    let ty = if let pt::Expression::New(_, ty) = ty.remove_parenthesis() {
+        ty
+    } else {
+        ty
+    };
+
+    let ty = ns.resolve_type(context.file_no, context.contract_no, false, ty, diagnostics)?;
+
+    match &ty {
+        Type::Array(ty, dim) => {
+            if matches!(dim.last(), Some(ArrayLength::Fixed(_))) {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "new cannot allocate fixed array type '{}'",
+                        ty.to_string(ns)
+                    ),
+                ));
+                return Err(());
+            }
+
+            if let Type::Contract(_) = ty.as_ref() {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!("new cannot construct array of '{}'", ty.to_string(ns)),
+                ));
+                return Err(());
+            }
+        }
+        Type::String | Type::DynamicBytes => {}
+        Type::Contract(n) => {
+            let call_args =
+                parse_call_args(loc, &call_args, false, context, ns, symtable, diagnostics)?;
+
+            return constructor(loc, *n, args, call_args, context, ns, symtable, diagnostics);
+        }
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("new cannot allocate type '{}'", ty.to_string(ns)),
+            ));
+            return Err(());
+        }
+    };
+
+    if let Some(loc) = call_args_loc {
+        diagnostics.push(Diagnostic::error(
+            loc,
+            "constructor arguments not permitted for allocation".to_string(),
+        ));
+        return Err(());
+    }
+
+    if args.len() != 1 {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            "new dynamic array should have a single length argument".to_string(),
+        ));
+        return Err(());
+    }
+
+    let size_loc = args[0].loc();
+    let expected_ty = Type::Uint(32);
+
+    let size_expr = expression(
+        &args[0],
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Type(&expected_ty),
+    )?;
+
+    used_variable(ns, &size_expr, symtable);
+
+    let size_ty = size_expr.ty();
+
+    if !matches!(size_ty.deref_any(), Type::Uint(_)) {
+        diagnostics.push(Diagnostic::error(
+            size_expr.loc(),
+            "new dynamic array should have an unsigned length argument".to_string(),
+        ));
+        return Err(());
+    }
+
+    let size = if size_ty.deref_any().bits(ns) > 32 {
+        diagnostics.push(Diagnostic::warning(
+            size_expr.loc(),
+            format!(
+                "conversion truncates {} to {}, as memory size is type {} on target {}",
+                size_ty.deref_any().to_string(ns),
+                expected_ty.to_string(ns),
+                expected_ty.to_string(ns),
+                ns.target
+            ),
+        ));
+
+        Expression::CheckingTrunc {
+            loc: size_loc,
+            to: expected_ty.clone(),
+            expr: Box::new(size_expr.cast(&size_loc, &size_ty, true, ns, diagnostics)?),
+        }
+    } else {
+        size_expr.cast(&size_loc, &expected_ty, true, ns, diagnostics)?
+    };
+
+    Ok(Expression::AllocDynamicBytes {
+        loc: *loc,
+        ty,
+        length: Box::new(size),
+        init: None,
+    })
+}
+
+/// Is it an (new C).value(1).gas(2)(1, 2, 3) style constructor (not supported)?
+pub(super) fn deprecated_constructor_arguments(
+    expr: &pt::Expression,
+    diagnostics: &mut Diagnostics,
+) -> Result<(), ()> {
+    match expr.remove_parenthesis() {
+        pt::Expression::FunctionCall(func_loc, ty, _) => {
+            if let pt::Expression::MemberAccess(_, ty, call_arg) = ty.as_ref() {
+                if deprecated_constructor_arguments(ty, diagnostics).is_err() {
+                    // location should be the identifier and the arguments
+                    let mut loc = call_arg.loc;
+                    if let pt::Loc::File(_, _, end) = &mut loc {
+                        *end = func_loc.end();
+                    }
+                    diagnostics.push(Diagnostic::error(
+                        loc,
+                        format!("deprecated call argument syntax '.{}(...)' is not supported, use '{{{}: ...}}' instead", call_arg.name, call_arg.name)
+                    ));
+                    return Err(());
+                }
+            }
+        }
+        pt::Expression::New(..) => {
+            return Err(());
+        }
+        _ => (),
+    }
+
+    Ok(())
+}

+ 2452 - 0
src/sema/expression/function_call.rs

@@ -0,0 +1,2452 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{
+    ArrayLength, Builtin, CallArgs, CallTy, Expression, 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};
+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::symtable::Symtable;
+use crate::sema::unused_variable::check_function_call;
+use crate::sema::{builtin, using};
+use crate::Target;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+use std::collections::HashMap;
+
+/// Resolve a function call via function type
+/// Function types do not have names so call cannot be using named parameters
+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: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    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();
+
+        // check if arguments can be implicitly casted
+        for (i, arg) in args.iter().enumerate() {
+            let arg = expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&params[i]),
+            )?;
+
+            cast_args.push(arg.cast(&arg.loc(), &params[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, 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();
+
+        // check if arguments can be implicitly casted
+        for (i, arg) in args.iter().enumerate() {
+            let arg = expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&params[i]),
+            )?;
+
+            cast_args.push(arg.cast(&arg.loc(), &params[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(())
+    }
+}
+
+/// Create a list of functions that can be called in this context. If global is true, then
+/// include functions outside of contracts
+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 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));
+        }
+    }
+
+    if let Some(contract_no) = contract_no {
+        list.extend(
+            ns.contracts[contract_no]
+                .all_functions
+                .keys()
+                .filter_map(|func_no| {
+                    if ns.functions[*func_no].name == name && ns.functions[*func_no].has_body {
+                        Some(*func_no)
+                    } else {
+                        None
+                    }
+                }),
+        );
+    }
+
+    list
+}
+
+/// Create a list of functions that can be called via super
+pub fn available_super_functions(name: &str, contract_no: usize, ns: &Namespace) -> Vec<usize> {
+    let mut list = Vec::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.name == name && func.has_body {
+                        Some(*func_no)
+                    } else {
+                        None
+                    }
+                }),
+        );
+    }
+
+    list
+}
+
+/// Resolve a function call with positional arguments
+pub fn function_call_pos_args(
+    loc: &pt::Loc,
+    id: &pt::Identifier,
+    func_ty: pt::FunctionTy,
+    args: &[pt::Expression],
+    function_nos: Vec<usize>,
+    virtual_call: bool,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    resolve_to: ResolveTo,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let mut name_matches = 0;
+    let mut errors = Diagnostics::default();
+
+    // Try to resolve as a function call
+    for function_no in &function_nos {
+        let func = &ns.functions[*function_no];
+
+        if func.ty != func_ty {
+            continue;
+        }
+
+        name_matches += 1;
+
+        let params_len = func.params.len();
+
+        if params_len != args.len() {
+            errors.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "{} expects {} arguments, {} provided",
+                    func.ty,
+                    params_len,
+                    args.len()
+                ),
+            ));
+            continue;
+        }
+
+        let mut matches = true;
+        let mut cast_args = Vec::new();
+
+        // check if arguments can be implicitly casted
+        for (i, arg) in args.iter().enumerate() {
+            let ty = ns.functions[*function_no].params[i].ty.clone();
+
+            let arg = match expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                &mut errors,
+                ResolveTo::Type(&ty),
+            ) {
+                Ok(e) => e,
+                Err(_) => {
+                    matches = false;
+                    continue;
+                }
+            };
+
+            match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
+                Ok(expr) => cast_args.push(expr),
+                Err(_) => {
+                    matches = false;
+                }
+            }
+        }
+
+        if !matches {
+            if function_nos.len() > 1 && diagnostics.extend_non_casting(&errors) {
+                return Err(());
+            }
+
+            continue;
+        }
+
+        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,
+                format!("declaration of {} '{}'", func.ty, func.name),
+            ));
+
+            continue;
+        }
+
+        let returns = function_returns(func, resolve_to);
+        let ty = function_type(func, false, resolve_to);
+
+        return Ok(Expression::InternalFunctionCall {
+            loc: *loc,
+            returns,
+            function: Box::new(Expression::InternalFunction {
+                loc: *loc,
+                ty,
+                function_no: *function_no,
+                signature: if virtual_call && (func.is_virtual || func.is_override.is_some()) {
+                    Some(func.signature.clone())
+                } else {
+                    None
+                },
+            }),
+            args: cast_args,
+        });
+    }
+
+    match name_matches {
+        0 => {
+            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),
+                ));
+            }
+        }
+        1 => diagnostics.extend(errors),
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("cannot find overloaded {} which matches signature", func_ty),
+            ));
+        }
+    }
+
+    Err(())
+}
+
+/// Resolve a function call with named arguments
+pub(super) fn function_call_named_args(
+    loc: &pt::Loc,
+    id: &pt::Identifier,
+    args: &[pt::NamedArgument],
+    function_nos: Vec<usize>,
+    virtual_call: bool,
+    context: &ExprContext,
+    resolve_to: ResolveTo,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let mut arguments = HashMap::new();
+
+    for arg in args {
+        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,
+            );
+        }
+
+        arguments.insert(arg.name.name.as_str(), &arg.expr);
+    }
+    // Try to resolve as a function call
+    let mut errors = Diagnostics::default();
+
+    // Try to resolve as a function call
+    for function_no in &function_nos {
+        let func = &ns.functions[*function_no];
+
+        if func.ty != pt::FunctionTy::Function {
+            continue;
+        }
+
+        let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
+        let params_len = func.params.len();
+        let mut matches = true;
+
+        if unnamed_params > 0 {
+            errors.push(Diagnostic::cast_error_with_note(
+                *loc,
+                format!(
+                    "function cannot be called with named arguments as {} of its parameters do not have names",
+                    unnamed_params,
+                ),
+                func.loc,
+                format!("definition of {}", func.name),
+            ));
+            matches = false;
+        } else if params_len != args.len() {
+            errors.push(Diagnostic::cast_error(
+                *loc,
+                format!(
+                    "function expects {} arguments, {} provided",
+                    params_len,
+                    args.len()
+                ),
+            ));
+            matches = false;
+        }
+
+        let mut cast_args = Vec::new();
+
+        // check if arguments can be implicitly casted
+        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 => {
+                    matches = false;
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "missing argument '{}' to function '{}'",
+                            param.name_as_str(),
+                            id.name,
+                        ),
+                    ));
+                    continue;
+                }
+            };
+
+            let ty = param.ty.clone();
+
+            let arg = match expression(
+                arg,
+                context,
+                ns,
+                symtable,
+                &mut errors,
+                ResolveTo::Type(&ty),
+            ) {
+                Ok(e) => e,
+                Err(()) => {
+                    matches = false;
+                    continue;
+                }
+            };
+
+            match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
+                Ok(expr) => cast_args.push(expr),
+                Err(_) => {
+                    matches = false;
+                }
+            }
+        }
+
+        if !matches {
+            if diagnostics.extend_non_casting(&errors) {
+                return Err(());
+            }
+            continue;
+        }
+
+        let func = &ns.functions[*function_no];
+
+        if func.contract_no != context.contract_no && func.is_private() {
+            errors.push(Diagnostic::error_with_note(
+                *loc,
+                "cannot call private function".to_string(),
+                func.loc,
+                format!("declaration of function '{}'", func.name),
+            ));
+
+            continue;
+        }
+
+        let returns = function_returns(func, resolve_to);
+        let ty = function_type(func, false, resolve_to);
+
+        return Ok(Expression::InternalFunctionCall {
+            loc: *loc,
+            returns,
+            function: Box::new(Expression::InternalFunction {
+                loc: *loc,
+                ty,
+                function_no: *function_no,
+                signature: if virtual_call && (func.is_virtual || func.is_override.is_some()) {
+                    Some(func.signature.clone())
+                } else {
+                    None
+                },
+            }),
+            args: cast_args,
+        });
+    }
+
+    match function_nos.len() {
+        0 => {
+            diagnostics.push(Diagnostic::error(
+                id.loc,
+                format!("unknown function or type '{}'", id.name),
+            ));
+        }
+        1 => diagnostics.extend(errors),
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "cannot find overloaded function which matches signature".to_string(),
+            ));
+        }
+    }
+
+    Err(())
+}
+
+/// Resolve a method call with positional arguments
+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: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    if let pt::Expression::Variable(namespace) = var {
+        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 builtin::resolve_namespace_call(
+                loc,
+                &namespace.name,
+                &func.name,
+                args,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+            );
+        }
+
+        // is it a call to super
+        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_pos_args(
+                    loc,
+                    func,
+                    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(());
+            }
+        }
+
+        // library or base contract call
+        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_pos_args(
+                    loc,
+                    func,
+                    pt::FunctionTy::Function,
+                    args,
+                    available_functions(
+                        &func.name,
+                        false,
+                        context.file_no,
+                        Some(call_contract_no),
+                        ns,
+                    ),
+                    true,
+                    context,
+                    ns,
+                    resolve_to,
+                    symtable,
+                    diagnostics,
+                );
+            }
+
+            // is a base contract of us
+            if let Some(contract_no) = context.contract_no {
+                if is_base(call_contract_no, contract_no, ns) {
+                    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_pos_args(
+                        loc,
+                        func,
+                        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 {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        "function calls via contract name are only valid for base contracts".into(),
+                    ));
+                }
+            }
+        }
+    }
+
+    if let Ok(Type::UserType(no)) = ns.resolve_type(
+        context.file_no,
+        context.contract_no,
+        false,
+        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(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(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![user_ty],
+                    kind: Builtin::UserTypeWrap,
+                    args: vec![expr.cast(&expr.loc(), &elem_ty, true, ns, diagnostics)?],
+                })
+            };
+        }
+    }
+
+    if let Some(mut path) = ns.expr_to_identifier_path(var) {
+        path.identifiers.push(func.clone());
+
+        if let Ok(list) = ns.resolve_free_function_with_namespace(
+            context.file_no,
+            &path,
+            &mut Diagnostics::default(),
+        ) {
+            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,
+                func,
+                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(expr) =
+        builtin::resolve_method_call(&var_expr, func, args, context, ns, symtable, diagnostics)?
+    {
+        return Ok(expr);
+    }
+
+    let var_ty = var_expr.ty();
+
+    if matches!(var_ty, Type::Bytes(_) | Type::String) && 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(());
+            }
+
+            string_format(loc, bs, args, context, ns, symtable, diagnostics)
+        } else {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "format only allowed on string literals".to_string(),
+            ));
+            Err(())
+        };
+    }
+
+    if let Type::StorageRef(immutable, ty) = &var_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];
+
+                    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(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(Expression::Builtin {
+                        loc: func.loc,
+                        tys: vec![return_ty],
+                        kind: Builtin::ArrayPop,
+                        args: vec![var_expr],
+                    });
+                }
+            }
+            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];
+
+                    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(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(Expression::Builtin {
+                        loc: func.loc,
+                        tys: vec![Type::Bytes(1)],
+                        kind: Builtin::ArrayPop,
+                        args: vec![var_expr],
+                    });
+                }
+            }
+            _ => {}
+        }
+    }
+
+    if matches!(var_ty, Type::Array(..) | Type::DynamicBytes) {
+        if func.name == "push" {
+            let elem_ty = var_ty.array_elem();
+
+            let val = match args.len() {
+                0 => {
+                    return Ok(Expression::Builtin {
+                        loc: *loc,
+                        tys: vec![elem_ty.clone()],
+                        kind: Builtin::ArrayPush,
+                        args: vec![var_expr],
+                    });
+                }
+                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(Expression::Builtin {
+                loc: *loc,
+                tys: vec![elem_ty.clone()],
+                kind: Builtin::ArrayPush,
+                args: vec![var_expr, 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(Expression::Builtin {
+                loc: *loc,
+                tys: vec![elem_ty.clone()],
+                kind: Builtin::ArrayPop,
+                args: vec![var_expr],
+            });
+        }
+    }
+
+    if let Type::Contract(ext_contract_no) = &var_ty.deref_any() {
+        let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?;
+
+        let mut errors = Diagnostics::default();
+        let mut name_matches: Vec<usize> = Vec::new();
+
+        for function_no in ns.contracts[*ext_contract_no].all_functions.keys() {
+            if func.name != ns.functions[*function_no].name
+                || ns.functions[*function_no].ty != pt::FunctionTy::Function
+            {
+                continue;
+            }
+
+            name_matches.push(*function_no);
+        }
+
+        for function_no in &name_matches {
+            let params_len = ns.functions[*function_no].params.len();
+
+            if params_len != args.len() {
+                errors.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "function expects {} arguments, {} provided",
+                        params_len,
+                        args.len()
+                    ),
+                ));
+                continue;
+            }
+
+            let mut matches = true;
+            let mut cast_args = Vec::new();
+
+            // check if arguments can be implicitly casted
+            for (i, arg) in args.iter().enumerate() {
+                let ty = ns.functions[*function_no].params[i].ty.clone();
+
+                let arg = match expression(
+                    arg,
+                    context,
+                    ns,
+                    symtable,
+                    &mut errors,
+                    ResolveTo::Type(&ty),
+                ) {
+                    Ok(e) => e,
+                    Err(_) => {
+                        matches = false;
+                        continue;
+                    }
+                };
+
+                match arg.cast(&arg.loc(), &ty, true, ns, &mut errors) {
+                    Ok(expr) => cast_args.push(expr),
+                    Err(()) => {
+                        matches = false;
+                        continue;
+                    }
+                }
+            }
+
+            if matches {
+                if !ns.functions[*function_no].is_public() {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!("function '{}' is not 'public' or 'external'", func.name),
+                    ));
+                    return Err(());
+                }
+
+                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);
+
+                return Ok(Expression::ExternalFunctionCall {
+                    loc: *loc,
+                    returns,
+                    function: Box::new(Expression::ExternalFunction {
+                        loc: *loc,
+                        ty,
+                        function_no: *function_no,
+                        address: Box::new(var_expr.cast(
+                            &var.loc(),
+                            &Type::Contract(func.contract_no.unwrap()),
+                            true,
+                            ns,
+                            diagnostics,
+                        )?),
+                    }),
+                    args: cast_args,
+                    call_args,
+                });
+            } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) {
+                return Err(());
+            }
+        }
+
+        // what about call args
+        match using::try_resolve_using_call(
+            loc,
+            func,
+            &var_expr,
+            context,
+            args,
+            symtable,
+            diagnostics,
+            ns,
+            resolve_to,
+        ) {
+            Ok(Some(expr)) => {
+                return Ok(expr);
+            }
+            Ok(None) => (),
+            Err(_) => {
+                return Err(());
+            }
+        }
+
+        if name_matches.len() == 1 {
+            diagnostics.extend(errors);
+        } else if name_matches.len() != 1 {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "cannot find overloaded function which matches signature".to_string(),
+            ));
+        }
+
+        return Err(());
+    }
+
+    if let Type::Address(is_payable) = &var_ty.deref_any() {
+        if func.name == "transfer" || func.name == "send" {
+            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(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![Type::Void],
+                    kind: Builtin::PayableTransfer,
+                    args: vec![address, value],
+                })
+            } else {
+                Ok(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![Type::Bool],
+                    kind: Builtin::PayableSend,
+                    args: vec![address, value],
+                })
+            };
+        }
+    }
+
+    if let Type::Address(payable) = &var_ty.deref_any() {
+        let ty = match func.name.as_str() {
+            "call" => Some(CallTy::Regular),
+            "delegatecall" if ns.target == Target::EVM => 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, true, context, ns, symtable, diagnostics)?;
+
+            if ty != CallTy::Regular && call_args.value.is_some() {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!("'{}' cannot have value specifed", func.name,),
+                ));
+
+                return Err(());
+            }
+
+            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(Expression::ExternalFunctionCallRaw {
+                loc: *loc,
+                ty,
+                args: Box::new(args),
+                address: Box::new(var_expr.cast(
+                    &var_expr.loc(),
+                    &Type::Address(*payable),
+                    true,
+                    ns,
+                    diagnostics,
+                )?),
+                call_args,
+            });
+        }
+    }
+
+    // resolve it using library extension
+    match using::try_resolve_using_call(
+        loc,
+        func,
+        &var_expr,
+        context,
+        args,
+        symtable,
+        diagnostics,
+        ns,
+        resolve_to,
+    ) {
+        Ok(Some(expr)) => {
+            return Ok(expr);
+        }
+        Ok(None) => (),
+        Err(_) => {
+            return Err(());
+        }
+    }
+
+    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: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    if let pt::Expression::Variable(namespace) = var {
+        // is it a call to super
+        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,
+                    func_name,
+                    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(());
+            }
+        }
+
+        // library or base contract call
+        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,
+                    func_name,
+                    args,
+                    available_functions(
+                        &func_name.name,
+                        false,
+                        context.file_no,
+                        Some(call_contract_no),
+                        ns,
+                    ),
+                    true,
+                    context,
+                    resolve_to,
+                    ns,
+                    symtable,
+                    diagnostics,
+                );
+            }
+
+            // is a base contract of us
+            if let Some(contract_no) = context.contract_no {
+                if is_base(call_contract_no, contract_no, ns) {
+                    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,
+                        func_name,
+                        args,
+                        available_functions(
+                            &func_name.name,
+                            false,
+                            context.file_no,
+                            Some(call_contract_no),
+                            ns,
+                        ),
+                        false,
+                        context,
+                        resolve_to,
+                        ns,
+                        symtable,
+                        diagnostics,
+                    );
+                } else {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        "function calls via contract name are only valid for base contracts".into(),
+                    ));
+                }
+            }
+        }
+    }
+
+    if let Some(mut path) = ns.expr_to_identifier_path(var) {
+        path.identifiers.push(func_name.clone());
+
+        if let Ok(list) = ns.resolve_free_function_with_namespace(
+            context.file_no,
+            &path,
+            &mut Diagnostics::default(),
+        ) {
+            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,
+                func_name,
+                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() {
+        let call_args = parse_call_args(loc, call_args, true, context, ns, symtable, diagnostics)?;
+
+        let mut arguments = HashMap::new();
+
+        // check if the arguments are not garbage
+        for arg in args {
+            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,
+                );
+
+                continue;
+            }
+
+            arguments.insert(arg.name.name.as_str(), &arg.expr);
+        }
+
+        let mut errors = Diagnostics::default();
+        let mut name_matches: Vec<usize> = Vec::new();
+
+        // function call
+        for function_no in ns.contracts[*external_contract_no].all_functions.keys() {
+            if ns.functions[*function_no].name != func_name.name
+                || ns.functions[*function_no].ty != pt::FunctionTy::Function
+            {
+                continue;
+            }
+
+            name_matches.push(*function_no);
+        }
+
+        for function_no in &name_matches {
+            let func = &ns.functions[*function_no];
+
+            let unnamed_params = func.params.iter().filter(|p| p.id.is_none()).count();
+            let params_len = func.params.len();
+
+            let mut matches = true;
+
+            if unnamed_params > 0 {
+                errors.push(Diagnostic::cast_error_with_note(
+                    *loc,
+                    format!(
+                        "function cannot be called with named arguments as {} of its parameters do not have names",
+                        unnamed_params,
+                    ),
+                    func.loc,
+                    format!("definition of {}", func.name),
+                ));
+                matches = false;
+            } else if params_len != args.len() {
+                errors.push(Diagnostic::cast_error(
+                    *loc,
+                    format!(
+                        "function expects {} arguments, {} provided",
+                        params_len,
+                        args.len()
+                    ),
+                ));
+                matches = false;
+            }
+            let mut cast_args = Vec::new();
+
+            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 => {
+                        matches = false;
+                        diagnostics.push(Diagnostic::cast_error(
+                            *loc,
+                            format!(
+                                "missing argument '{}' to function '{}'",
+                                param.name_as_str(),
+                                func_name.name,
+                            ),
+                        ));
+                        continue;
+                    }
+                };
+
+                let arg = match expression(
+                    arg,
+                    context,
+                    ns,
+                    symtable,
+                    &mut errors,
+                    ResolveTo::Type(&param.ty),
+                ) {
+                    Ok(e) => e,
+                    Err(()) => {
+                        matches = false;
+                        continue;
+                    }
+                };
+
+                match arg.cast(&arg.loc(), &param.ty, true, ns, &mut errors) {
+                    Ok(expr) => cast_args.push(expr),
+                    Err(()) => {
+                        matches = false;
+                        break;
+                    }
+                }
+            }
+
+            if matches {
+                if !ns.functions[*function_no].is_public() {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "function '{}' is not 'public' or 'external'",
+                            func_name.name
+                        ),
+                    ));
+                } 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.name
+                            ),
+                        ));
+                    }
+                }
+
+                let func = &ns.functions[*function_no];
+                let returns = function_returns(func, resolve_to);
+                let ty = function_type(func, true, resolve_to);
+
+                return Ok(Expression::ExternalFunctionCall {
+                    loc: *loc,
+                    returns,
+                    function: Box::new(Expression::ExternalFunction {
+                        loc: *loc,
+                        ty,
+                        function_no: *function_no,
+                        address: Box::new(var_expr.cast(
+                            &var.loc(),
+                            &Type::Contract(func.contract_no.unwrap()),
+                            true,
+                            ns,
+                            diagnostics,
+                        )?),
+                    }),
+                    args: cast_args,
+                    call_args,
+                });
+            } else if name_matches.len() > 1 && diagnostics.extend_non_casting(&errors) {
+                return Err(());
+            }
+        }
+
+        match name_matches.len() {
+            0 => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "contract '{}' does not have function '{}'",
+                        var_ty.deref_any().to_string(ns),
+                        func_name.name
+                    ),
+                ));
+            }
+            1 => diagnostics.extend(errors),
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "cannot find overloaded function which matches signature".to_string(),
+                ));
+            }
+        }
+        return Err(());
+    }
+
+    diagnostics.push(Diagnostic::error(
+        func_name.loc,
+        format!("method '{}' does not exist", func_name.name),
+    ));
+
+    Err(())
+}
+
+/// Function call arguments
+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))
+}
+
+/// Parse call arguments for external calls
+pub(super) fn parse_call_args(
+    loc: &pt::Loc,
+    call_args: &[&pt::NamedArgument],
+    external_call: bool,
+    context: &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,
+                )?));
+            }
+            "address" => {
+                if ns.target != Target::Solana {
+                    diagnostics.push(Diagnostic::error(
+                        arg.loc,
+                        format!(
+                            "'address' not permitted for external calls or constructors on {}",
+                            ns.target
+                        ),
+                    ));
+                    return Err(());
+                }
+
+                if external_call {
+                    diagnostics.push(Diagnostic::error(
+                        arg.loc,
+                        "'address' not valid for external calls".to_string(),
+                    ));
+                    return Err(());
+                }
+
+                let ty = Type::Address(false);
+
+                let expr = expression(
+                    &arg.expr,
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Type(&ty),
+                )?;
+
+                res.address = 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::Uint(256);
+
+                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(());
+                }
+
+                let expr = expression(
+                    &arg.expr,
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Unknown,
+                )?;
+
+                let mut correct_ty = false;
+                let expr_ty = expr.ty();
+
+                // if let chains would really help here
+                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(());
+                }
+
+                res.accounts = Some(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(Box::new(Type::Slice(Box::new(Type::Bytes(1)))));
+
+                let expr = expression(
+                    &arg.expr,
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Type(&ty),
+                )?;
+
+                res.seeds = Some(Box::new(expr));
+            }
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    arg.loc,
+                    format!("'{}' not a valid call parameter", arg.name.name),
+                ));
+                return Err(());
+            }
+        }
+    }
+
+    // address is required on solana constructors
+    if ns.target == Target::Solana && !external_call && res.address.is_none() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!("'address' call argument required on {}", ns.target),
+        ));
+        return Err(());
+    }
+
+    Ok(res)
+}
+
+pub fn named_call_expr(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::NamedArgument],
+    is_destructible: bool,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut nullsink = Diagnostics::default();
+
+    // is it a struct literal
+    match ns.resolve_type(
+        context.file_no,
+        context.contract_no,
+        true,
+        ty,
+        &mut nullsink,
+    ) {
+        Ok(Type::Struct(str_ty)) => {
+            return named_struct_literal(loc, &str_ty, args, context, ns, symtable, diagnostics);
+        }
+        Ok(_) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "struct or function expected".to_string(),
+            ));
+            return Err(());
+        }
+        _ => {}
+    }
+
+    // not a struct literal, remove those errors and try resolving as function call
+    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)
+}
+
+/// Resolve any callable expression
+pub fn call_expr(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::Expression],
+    is_destructible: bool,
+    context: &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,
+        true,
+        ty,
+        &mut nullsink,
+    ) {
+        Ok(Type::Struct(str_ty)) => {
+            return struct_literal(loc, &str_ty, args, context, ns, symtable, diagnostics);
+        }
+        Ok(to) => {
+            // Cast
+            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)
+}
+
+/// Resolve function call
+pub fn function_call_expr(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::Expression],
+    context: &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) => {
+            if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "cannot call function in constant expression".to_string(),
+                ));
+                return Err(());
+            }
+
+            method_call_pos_args(
+                loc,
+                member,
+                func,
+                args,
+                &call_args,
+                call_args_loc,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                resolve_to,
+            )
+        }
+        pt::Expression::Variable(id) => {
+            // is it a builtin
+            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 context.constant {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "cannot call function in constant expression".to_string(),
+                ));
+                return Err(());
+            }
+
+            // is there a local variable or contract variable with this name
+            if symtable.find(&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(());
+                }
+
+                function_call_pos_args(
+                    loc,
+                    id,
+                    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,
+        ),
+    }
+}
+
+/// Resolve function call expression with named arguments
+pub fn named_function_call_expr(
+    loc: &pt::Loc,
+    ty: &pt::Expression,
+    args: &[pt::NamedArgument],
+    context: &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(());
+            }
+
+            function_call_named_args(
+                loc,
+                id,
+                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(())
+        }
+    }
+}
+
+/// Get the return values for a function call
+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]
+    }
+}
+
+/// Get the function type for an internal.external function call
+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,
+        }
+    }
+}

+ 227 - 0
src/sema/expression/integers.rs

@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, Namespace, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::ResolveTo;
+use num_bigint::{BigInt, Sign};
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+
+pub(super) fn coerce(
+    l: &Type,
+    l_loc: &pt::Loc,
+    r: &Type,
+    r_loc: &pt::Loc,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<Type, ()> {
+    let l = match l {
+        Type::Ref(ty) => ty,
+        Type::StorageRef(_, ty) => ty,
+        _ => l,
+    };
+    let r = match r {
+        Type::Ref(ty) => ty,
+        Type::StorageRef(_, ty) => ty,
+        _ => r,
+    };
+
+    if *l == *r {
+        return Ok(l.clone());
+    }
+
+    // Address payable is implicitly convertible to address, so we can compare these
+    if *l == Type::Address(false) && *r == Type::Address(true)
+        || *l == Type::Address(true) && *r == Type::Address(false)
+    {
+        return Ok(Type::Address(false));
+    }
+
+    coerce_number(l, l_loc, r, r_loc, true, false, ns, diagnostics)
+}
+
+pub(super) fn get_int_length(
+    l: &Type,
+    l_loc: &pt::Loc,
+    allow_bytes: bool,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<(u16, bool), ()> {
+    match l {
+        Type::Uint(n) => Ok((*n, false)),
+        Type::Int(n) => Ok((*n, true)),
+        Type::Value => Ok((ns.value_length as u16 * 8, false)),
+        Type::Bytes(n) if allow_bytes => Ok((*n as u16 * 8, false)),
+        Type::Enum(n) => {
+            diagnostics.push(Diagnostic::error(
+                *l_loc,
+                format!("type enum {} not allowed", ns.enums[*n]),
+            ));
+            Err(())
+        }
+        Type::Struct(str_ty) => {
+            diagnostics.push(Diagnostic::error(
+                *l_loc,
+                format!("type struct {} not allowed", str_ty.definition(ns)),
+            ));
+            Err(())
+        }
+        Type::Array(..) => {
+            diagnostics.push(Diagnostic::error(
+                *l_loc,
+                format!("type array {} not allowed", l.to_string(ns)),
+            ));
+            Err(())
+        }
+        Type::Ref(n) => get_int_length(n, l_loc, allow_bytes, ns, diagnostics),
+        Type::StorageRef(_, n) => get_int_length(n, l_loc, allow_bytes, ns, diagnostics),
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *l_loc,
+                format!("expression of type {} not allowed", l.to_string(ns)),
+            ));
+            Err(())
+        }
+    }
+}
+
+pub fn coerce_number(
+    l: &Type,
+    l_loc: &pt::Loc,
+    r: &Type,
+    r_loc: &pt::Loc,
+    allow_bytes: bool,
+    for_compare: bool,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<Type, ()> {
+    let l = match l {
+        Type::Ref(ty) => ty,
+        Type::StorageRef(_, ty) => ty,
+        _ => l,
+    };
+    let r = match r {
+        Type::Ref(ty) => ty,
+        Type::StorageRef(_, ty) => ty,
+        _ => r,
+    };
+
+    match (l, r) {
+        (Type::Address(false), Type::Address(false)) if for_compare => {
+            return Ok(Type::Address(false));
+        }
+        (Type::Address(true), Type::Address(true)) if for_compare => {
+            return Ok(Type::Address(true));
+        }
+        (Type::Contract(left), Type::Contract(right)) if left == right && for_compare => {
+            return Ok(Type::Contract(*left));
+        }
+        (Type::Bytes(left_length), Type::Bytes(right_length)) if allow_bytes => {
+            return Ok(Type::Bytes(std::cmp::max(*left_length, *right_length)));
+        }
+        (Type::Bytes(_), _) if allow_bytes => {
+            return Ok(l.clone());
+        }
+        (_, Type::Bytes(_)) if allow_bytes => {
+            return Ok(r.clone());
+        }
+        (Type::Rational, Type::Int(_)) => {
+            return Ok(Type::Rational);
+        }
+        (Type::Rational, Type::Rational) => {
+            return Ok(Type::Rational);
+        }
+        (Type::Rational, Type::Uint(_)) => {
+            return Ok(Type::Rational);
+        }
+        (Type::Uint(_), Type::Rational) => {
+            return Ok(Type::Rational);
+        }
+        (Type::Int(_), Type::Rational) => {
+            return Ok(Type::Rational);
+        }
+        (Type::Bool, Type::Int(_) | Type::Uint(_)) => {
+            return Ok(r.clone());
+        }
+        (Type::Int(_) | Type::Uint(_), Type::Bool) => {
+            return Ok(l.clone());
+        }
+        _ => (),
+    }
+
+    let (left_len, left_signed) = get_int_length(l, l_loc, false, ns, diagnostics)?;
+
+    let (right_len, right_signed) = get_int_length(r, r_loc, false, ns, diagnostics)?;
+
+    Ok(match (left_signed, right_signed) {
+        (true, true) => Type::Int(left_len.max(right_len)),
+        (false, false) => Type::Uint(left_len.max(right_len)),
+        (true, false) => {
+            // uint8 fits into int16
+            let len = left_len.max(right_len + 8);
+
+            Type::Int(len.min(256))
+        }
+        (false, true) => {
+            // uint8 fits into int16
+            let len = (left_len + 8).max(right_len);
+
+            Type::Int(len.min(256))
+        }
+    })
+}
+
+/// Try to convert a BigInt into a Expression::NumberLiteral.
+pub fn bigint_to_expression(
+    loc: &pt::Loc,
+    n: &BigInt,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let bits = n.bits();
+
+    if let ResolveTo::Type(resolve_to) = resolve_to {
+        if *resolve_to != Type::Unresolved {
+            if !resolve_to.is_integer() {
+                diagnostics.push(Diagnostic::cast_error(
+                    *loc,
+                    format!("expected '{}', found integer", resolve_to.to_string(ns)),
+                ));
+                return Err(());
+            } else {
+                return Ok(Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: resolve_to.clone(),
+                    value: n.clone(),
+                });
+            }
+        }
+    }
+
+    // Return smallest type
+
+    let int_size = if bits < 7 { 8 } else { (bits + 7) & !7 } as u16;
+
+    if n.sign() == Sign::Minus {
+        if bits > 255 {
+            diagnostics.push(Diagnostic::error(*loc, format!("{} is too large", n)));
+            Err(())
+        } else {
+            Ok(Expression::NumberLiteral {
+                loc: *loc,
+                ty: Type::Int(int_size),
+                value: n.clone(),
+            })
+        }
+    } else if bits > 256 {
+        diagnostics.push(Diagnostic::error(*loc, format!("{} is too large", n)));
+        Err(())
+    } else {
+        Ok(Expression::NumberLiteral {
+            loc: *loc,
+            ty: Type::Uint(int_size),
+            value: n.clone(),
+        })
+    }
+}

+ 816 - 0
src/sema/expression/literals.rs

@@ -0,0 +1,816 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::address::to_hexstr_eip55;
+use crate::sema::ast::{ArrayLength, Expression, Namespace, RetrieveType, StructType, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::integers::bigint_to_expression;
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::strings::unescape;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::used_variable;
+use crate::Target;
+use base58::{FromBase58, FromBase58Error};
+use num_bigint::{BigInt, Sign};
+use num_rational::BigRational;
+use num_traits::{FromPrimitive, Num, Zero};
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::{CodeLocation, Loc};
+use std::ops::Mul;
+use std::str::FromStr;
+
+pub(super) fn string_literal(
+    v: &[pt::StringLiteral],
+    file_no: usize,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Expression {
+    // Concatenate the strings
+    let mut result = Vec::new();
+    let mut loc = v[0].loc;
+
+    for s in v {
+        result.append(&mut unescape(
+            &s.string,
+            s.loc.start(),
+            file_no,
+            diagnostics,
+        ));
+        loc.use_end_from(&s.loc);
+    }
+
+    let length = result.len();
+
+    match resolve_to {
+        ResolveTo::Type(Type::String) => Expression::AllocDynamicBytes {
+            loc,
+            ty: Type::String,
+            length: Box::new(Expression::NumberLiteral {
+                loc,
+                ty: Type::Uint(32),
+                value: BigInt::from(length),
+            }),
+            init: Some(result),
+        },
+        ResolveTo::Type(Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
+            Expression::AllocDynamicBytes {
+                loc,
+                ty: Type::Slice(ty.clone()),
+                length: Box::new(Expression::NumberLiteral {
+                    loc,
+                    ty: Type::Uint(32),
+                    value: BigInt::from(length),
+                }),
+                init: Some(result),
+            }
+        }
+        _ => Expression::BytesLiteral {
+            loc,
+            ty: Type::Bytes(length as u8),
+            value: result,
+        },
+    }
+}
+
+pub(super) fn hex_literal(
+    v: &[pt::HexLiteral],
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut result = Vec::new();
+    let mut loc = v[0].loc;
+
+    for s in v {
+        if (s.hex.len() % 2) != 0 {
+            diagnostics.push(Diagnostic::error(
+                s.loc,
+                format!("hex string \"{}\" has odd number of characters", s.hex),
+            ));
+            return Err(());
+        } else {
+            result.extend_from_slice(&hex::decode(&s.hex).unwrap());
+            loc.use_end_from(&s.loc);
+        }
+    }
+
+    let length = result.len();
+
+    match resolve_to {
+        ResolveTo::Type(Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
+            Ok(Expression::AllocDynamicBytes {
+                loc,
+                ty: Type::Slice(ty.clone()),
+                length: Box::new(Expression::NumberLiteral {
+                    loc,
+                    ty: Type::Uint(32),
+                    value: BigInt::from(length),
+                }),
+                init: Some(result),
+            })
+        }
+        ResolveTo::Type(Type::DynamicBytes) => Ok(Expression::AllocDynamicBytes {
+            loc,
+            ty: Type::DynamicBytes,
+            length: Box::new(Expression::NumberLiteral {
+                loc,
+                ty: Type::Uint(32),
+                value: BigInt::from(length),
+            }),
+            init: Some(result),
+        }),
+        _ => Ok(Expression::BytesLiteral {
+            loc,
+            ty: Type::Bytes(length as u8),
+            value: result,
+        }),
+    }
+}
+
+pub(crate) fn hex_number_literal(
+    loc: &pt::Loc,
+    n: &str,
+    ns: &mut Namespace,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    // ns.address_length is in bytes; double for hex and two for the leading 0x
+    if n.starts_with("0x") && !n.chars().any(|c| c == '_') && n.len() == 42 {
+        let address = to_hexstr_eip55(n);
+
+        if ns.target == Target::EVM {
+            return if address == *n {
+                let s: String = address.chars().skip(2).collect();
+
+                Ok(Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: Type::Address(false),
+                    value: BigInt::from_str_radix(&s, 16).unwrap(),
+                })
+            } else {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "address literal has incorrect checksum, expected '{}'",
+                        address
+                    ),
+                ));
+                Err(())
+            };
+        } else if address == *n {
+            // looks like ethereum address
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "ethereum address literal '{}' not supported on target {}",
+                    n, ns.target
+                ),
+            ));
+            return Err(());
+        }
+    }
+
+    // from_str_radix does not like the 0x prefix
+    let s: String = n.chars().skip(2).filter(|v| *v != '_').collect();
+
+    // hex values are allowed for bytesN but the length must match
+    if let ResolveTo::Type(Type::Bytes(length)) = resolve_to {
+        let expected_length = *length as usize * 2;
+        let val = BigInt::from_str_radix(&s, 16).unwrap();
+
+        return if !val.is_zero() && s.len() != expected_length {
+            diagnostics.push(Diagnostic::cast_error(
+                *loc,
+                format!(
+                    "hex literal {} must be {} digits for type 'bytes{}'",
+                    n, expected_length, length,
+                ),
+            ));
+            Err(())
+        } else {
+            Ok(Expression::NumberLiteral {
+                loc: *loc,
+                ty: Type::Bytes(*length),
+                value: val,
+            })
+        };
+    }
+
+    bigint_to_expression(
+        loc,
+        &BigInt::from_str_radix(&s, 16).unwrap(),
+        ns,
+        diagnostics,
+        resolve_to,
+    )
+}
+
+pub(super) fn address_literal(
+    loc: &pt::Loc,
+    address: &str,
+    ns: &mut Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    if ns.target.is_substrate() {
+        match address.from_base58() {
+            Ok(v) => {
+                if v.len() != ns.address_length + 3 {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "address literal {} incorrect length of {}",
+                            address,
+                            v.len()
+                        ),
+                    ));
+                    return Err(());
+                }
+
+                let hash_data: Vec<u8> = b"SS58PRE"
+                    .iter()
+                    .chain(v[..=ns.address_length].iter())
+                    .cloned()
+                    .collect();
+
+                let hash = blake2_rfc::blake2b::blake2b(64, &[], &hash_data);
+                let hash = hash.as_bytes();
+
+                if v[ns.address_length + 1] != hash[0] || v[ns.address_length + 2] != hash[1] {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!("address literal {} hash incorrect checksum", address,),
+                    ));
+                    return Err(());
+                }
+
+                Ok(Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: Type::Address(false),
+                    value: BigInt::from_bytes_be(Sign::Plus, &v[1..ns.address_length + 1]),
+                })
+            }
+            Err(FromBase58Error::InvalidBase58Length) => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!("address literal {} invalid base58 length", address),
+                ));
+                Err(())
+            }
+            Err(FromBase58Error::InvalidBase58Character(ch, pos)) => {
+                let mut loc = *loc;
+                if let pt::Loc::File(_, start, end) = &mut loc {
+                    *start += pos;
+                    *end = *start;
+                }
+                diagnostics.push(Diagnostic::error(
+                    loc,
+                    format!("address literal {} invalid character '{}'", address, ch),
+                ));
+                Err(())
+            }
+        }
+    } else if ns.target == Target::Solana {
+        match address.from_base58() {
+            Ok(v) => {
+                if v.len() != ns.address_length {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "address literal {} incorrect length of {}",
+                            address,
+                            v.len()
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::NumberLiteral {
+                        loc: *loc,
+                        ty: Type::Address(false),
+                        value: BigInt::from_bytes_be(Sign::Plus, &v),
+                    })
+                }
+            }
+            Err(FromBase58Error::InvalidBase58Length) => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!("address literal {} invalid base58 length", address),
+                ));
+                Err(())
+            }
+            Err(FromBase58Error::InvalidBase58Character(ch, pos)) => {
+                let mut loc = *loc;
+                if let pt::Loc::File(_, start, end) = &mut loc {
+                    *start += pos;
+                    *end = *start;
+                }
+                diagnostics.push(Diagnostic::error(
+                    loc,
+                    format!("address literal {} invalid character '{}'", address, ch),
+                ));
+                Err(())
+            }
+        }
+    } else {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!("address literal {} not supported on {}", address, ns.target),
+        ));
+        Err(())
+    }
+}
+
+/// Resolve the given number literal, multiplied by value of unit
+pub(crate) fn number_literal(
+    loc: &pt::Loc,
+    integer: &str,
+    exp: &str,
+    ns: &Namespace,
+    unit: &BigInt,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let integer = BigInt::from_str(integer).unwrap();
+
+    let n = if exp.is_empty() {
+        integer
+    } else {
+        let base10 = BigInt::from_str("10").unwrap();
+
+        if let Some(abs_exp) = exp.strip_prefix('-') {
+            if let Ok(exp) = u8::from_str(abs_exp) {
+                let res = BigRational::new(integer, base10.pow(exp.into()));
+
+                if res.is_integer() {
+                    res.to_integer()
+                } else {
+                    return Ok(Expression::RationalNumberLiteral {
+                        loc: *loc,
+                        ty: Type::Rational,
+                        value: res,
+                    });
+                }
+            } else {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!("exponent '{}' too large", exp),
+                ));
+                return Err(());
+            }
+        } else if let Ok(exp) = u8::from_str(exp) {
+            integer.mul(base10.pow(exp.into()))
+        } else {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("exponent '{}' too large", exp),
+            ));
+            return Err(());
+        }
+    };
+
+    bigint_to_expression(loc, &n.mul(unit), ns, diagnostics, resolve_to)
+}
+
+/// Resolve the given rational number literal, multiplied by value of unit
+pub(super) fn rational_number_literal(
+    loc: &pt::Loc,
+    integer: &str,
+    fraction: &str,
+    exp: &str,
+    unit: &BigInt,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut integer = integer.to_owned();
+    let len = fraction.len() as u32;
+    let exp_negative = exp.starts_with('-');
+
+    let denominator = BigInt::from_str("10").unwrap().pow(len);
+    let zero_index = fraction
+        .chars()
+        .position(|c| c != '0')
+        .unwrap_or(usize::MAX);
+    let n = if exp.is_empty() {
+        if integer.is_empty() || integer == "0" {
+            if zero_index < usize::MAX {
+                BigRational::new(
+                    BigInt::from_str(&fraction[zero_index..]).unwrap(),
+                    denominator,
+                )
+            } else {
+                BigRational::from(BigInt::zero())
+            }
+        } else {
+            integer.push_str(fraction);
+            BigRational::new(BigInt::from_str(&integer).unwrap(), denominator)
+        }
+    } else {
+        let exp = if let Ok(exp) = u8::from_str(if exp_negative { &exp[1..] } else { exp }) {
+            exp
+        } else {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!("exponent '{}' too large", exp),
+            ));
+            return Err(());
+        };
+        let exp_result = BigInt::from_str("10").unwrap().pow(exp.into());
+
+        if integer.is_empty() || integer == "0" {
+            if zero_index < usize::MAX {
+                if exp_negative {
+                    BigRational::new(
+                        BigInt::from_str(&fraction[zero_index..]).unwrap(),
+                        denominator.mul(exp_result),
+                    )
+                } else {
+                    BigRational::new(
+                        BigInt::from_str(&fraction[zero_index..])
+                            .unwrap()
+                            .mul(exp_result),
+                        denominator,
+                    )
+                }
+            } else {
+                BigRational::from(BigInt::zero())
+            }
+        } else {
+            integer.push_str(fraction);
+            if exp_negative {
+                BigRational::new(
+                    BigInt::from_str(&integer).unwrap(),
+                    denominator.mul(exp_result),
+                )
+            } else {
+                BigRational::new(
+                    BigInt::from_str(&integer).unwrap().mul(exp_result),
+                    denominator,
+                )
+            }
+        }
+    };
+
+    let res = n.mul(unit);
+
+    if res.is_integer() {
+        bigint_to_expression(loc, &res.to_integer(), ns, diagnostics, resolve_to)
+    } else {
+        Ok(Expression::RationalNumberLiteral {
+            loc: *loc,
+            ty: Type::Rational,
+            value: res,
+        })
+    }
+}
+
+/// Resolve a function call with positional arguments
+pub(super) fn struct_literal(
+    loc: &pt::Loc,
+    struct_ty: &StructType,
+    args: &[pt::Expression],
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let struct_def = struct_ty.definition(ns).clone();
+
+    let ty = Type::Struct(*struct_ty);
+
+    if ty.contains_builtins(ns, &StructType::AccountInfo).is_some() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "builtin struct '{}' cannot be created using struct literal",
+                struct_def.name,
+            ),
+        ));
+        Err(())
+    } else if args.len() != struct_def.fields.len() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "struct '{}' has {} fields, not {}",
+                struct_def.name,
+                struct_def.fields.len(),
+                args.len()
+            ),
+        ));
+        Err(())
+    } else {
+        let mut fields = Vec::new();
+
+        for (i, a) in args.iter().enumerate() {
+            let expr = expression(
+                a,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&struct_def.fields[i].ty),
+            )?;
+            used_variable(ns, &expr, symtable);
+            fields.push(expr.cast(loc, &struct_def.fields[i].ty, true, ns, diagnostics)?);
+        }
+
+        Ok(Expression::StructLiteral {
+            loc: *loc,
+            ty,
+            values: fields,
+        })
+    }
+}
+
+/// Resolve a struct literal with named fields
+pub(super) fn named_struct_literal(
+    loc: &pt::Loc,
+    str_ty: &StructType,
+    args: &[pt::NamedArgument],
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let struct_def = str_ty.definition(ns).clone();
+    let ty = Type::Struct(*str_ty);
+
+    if ty.contains_builtins(ns, &StructType::AccountInfo).is_some() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "builtin struct '{}' cannot be created using struct literal",
+                struct_def.name,
+            ),
+        ));
+        Err(())
+    } else if args.len() != struct_def.fields.len() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "struct '{}' has {} fields, not {}",
+                struct_def.name,
+                struct_def.fields.len(),
+                args.len()
+            ),
+        ));
+        Err(())
+    } else {
+        let mut fields = Vec::new();
+        fields.resize(
+            args.len(),
+            Expression::BoolLiteral {
+                loc: Loc::Implicit,
+                value: false,
+            },
+        );
+        for a in args {
+            match struct_def.fields.iter().enumerate().find(|(_, f)| {
+                f.id.as_ref().map(|id| id.name.as_str()) == Some(a.name.name.as_str())
+            }) {
+                Some((i, f)) => {
+                    let expr = expression(
+                        &a.expr,
+                        context,
+                        ns,
+                        symtable,
+                        diagnostics,
+                        ResolveTo::Type(&f.ty),
+                    )?;
+                    used_variable(ns, &expr, symtable);
+                    fields[i] = expr.cast(loc, &f.ty, true, ns, diagnostics)?;
+                }
+                None => {
+                    diagnostics.push(Diagnostic::error(
+                        a.name.loc,
+                        format!(
+                            "struct '{}' has no field '{}'",
+                            struct_def.name, a.name.name,
+                        ),
+                    ));
+                    return Err(());
+                }
+            }
+        }
+        Ok(Expression::StructLiteral {
+            loc: *loc,
+            ty,
+            values: fields,
+        })
+    }
+}
+
+/// Given an parsed literal array, ensure that it is valid. All the elements in the array
+/// must of the same type. The array might be a multidimensional array; all the leaf nodes
+/// must match.
+pub(super) fn array_literal(
+    loc: &pt::Loc,
+    exprs: &[pt::Expression],
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    let mut dimensions = Vec::new();
+    let mut flattened = Vec::new();
+
+    let resolve_to = match resolve_to {
+        ResolveTo::Type(Type::Array(elem_ty, _)) => ResolveTo::Type(elem_ty),
+        // Solana seeds are a slice of slice of bytes, e.g. [ [ "fo", "o" ], [ "b", "a", "r"]]. In this
+        // case we want to resolve
+        ResolveTo::Type(Type::Slice(slice)) if matches!(slice.as_ref(), Type::Slice(_)) => {
+            let mut res = Vec::new();
+            let mut has_errors = false;
+
+            for expr in exprs {
+                let expr = match expression(
+                    expr,
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Type(&Type::Array(slice.clone(), vec![ArrayLength::Dynamic])),
+                ) {
+                    Ok(expr) => expr,
+                    Err(_) => {
+                        has_errors = true;
+                        continue;
+                    }
+                };
+
+                let ty = expr.ty();
+
+                if let Type::Array(elem, dims) = &ty {
+                    if elem != slice || dims.len() != 1 {
+                        diagnostics.push(Diagnostic::error(
+                            expr.loc(),
+                            format!(
+                                "type {} found where array {} expected",
+                                elem.to_string(ns),
+                                slice.to_string(ns)
+                            ),
+                        ));
+                        has_errors = true;
+                    }
+                } else {
+                    diagnostics.push(Diagnostic::error(
+                        expr.loc(),
+                        format!(
+                            "type {} found where array of slices expected",
+                            ty.to_string(ns)
+                        ),
+                    ));
+                    has_errors = true;
+                }
+
+                res.push(expr);
+            }
+
+            return if has_errors {
+                Err(())
+            } else {
+                let aty = Type::Array(
+                    slice.clone(),
+                    vec![ArrayLength::Fixed(BigInt::from(exprs.len()))],
+                );
+
+                Ok(Expression::ArrayLiteral {
+                    loc: *loc,
+                    ty: aty,
+                    dimensions: vec![exprs.len() as u32],
+                    values: res,
+                })
+            };
+        }
+        _ => resolve_to,
+    };
+
+    check_subarrays(
+        exprs,
+        &mut Some(&mut dimensions),
+        &mut flattened,
+        diagnostics,
+    )?;
+
+    if flattened.is_empty() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            "array requires at least one element".to_string(),
+        ));
+        return Err(());
+    }
+
+    let mut flattened = flattened.iter();
+
+    // We follow the solidity scheme were everthing gets implicitly converted to the
+    // type of the first element
+    let mut first = expression(
+        flattened.next().unwrap(),
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        resolve_to,
+    )?;
+
+    let ty = if let ResolveTo::Type(ty) = resolve_to {
+        first = first.cast(&first.loc(), ty, true, ns, diagnostics)?;
+
+        ty.clone()
+    } else {
+        first.ty()
+    };
+
+    used_variable(ns, &first, symtable);
+    let mut exprs = vec![first];
+
+    for e in flattened {
+        let mut other = expression(e, context, ns, symtable, diagnostics, ResolveTo::Type(&ty))?;
+        used_variable(ns, &other, symtable);
+
+        if other.ty() != ty {
+            other = other.cast(&e.loc(), &ty, true, ns, diagnostics)?;
+        }
+
+        exprs.push(other);
+    }
+
+    let aty = Type::Array(
+        Box::new(ty),
+        dimensions
+            .iter()
+            .map(|n| ArrayLength::Fixed(BigInt::from_u32(*n).unwrap()))
+            .collect::<Vec<ArrayLength>>(),
+    );
+
+    if context.constant {
+        Ok(Expression::ConstArrayLiteral {
+            loc: *loc,
+            ty: aty,
+            dimensions,
+            values: exprs,
+        })
+    } else {
+        Ok(Expression::ArrayLiteral {
+            loc: *loc,
+            ty: aty,
+            dimensions,
+            values: exprs,
+        })
+    }
+}
+
+/// Traverse the literal looking for sub arrays. Ensure that all the sub
+/// arrays are the same length, and returned a flattened array of elements
+fn check_subarrays<'a>(
+    exprs: &'a [pt::Expression],
+    dims: &mut Option<&mut Vec<u32>>,
+    flatten: &mut Vec<&'a pt::Expression>,
+    diagnostics: &mut Diagnostics,
+) -> Result<(), ()> {
+    if let Some(pt::Expression::ArrayLiteral(_, first)) = exprs.get(0) {
+        // ensure all elements are array literals of the same length
+        check_subarrays(first, dims, flatten, diagnostics)?;
+
+        for (i, e) in exprs.iter().enumerate().skip(1) {
+            if let pt::Expression::ArrayLiteral(_, other) = e {
+                if other.len() != first.len() {
+                    diagnostics.push(Diagnostic::error(
+                        e.loc(),
+                        format!(
+                            "array elements should be identical, sub array {} has {} elements rather than {}", i + 1, other.len(), first.len()
+                        ),
+                    ));
+                    return Err(());
+                }
+                check_subarrays(other, &mut None, flatten, diagnostics)?;
+            } else {
+                diagnostics.push(Diagnostic::error(
+                    e.loc(),
+                    format!("array element {} should also be an array", i + 1),
+                ));
+                return Err(());
+            }
+        }
+    } else {
+        for (i, e) in exprs.iter().enumerate().skip(1) {
+            if let pt::Expression::ArrayLiteral(loc, _) = e {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "array elements should be of the type, element {} is unexpected array",
+                        i + 1
+                    ),
+                ));
+                return Err(());
+            }
+        }
+        flatten.extend(exprs);
+    }
+
+    if let Some(dims) = dims.as_deref_mut() {
+        dims.push(exprs.len() as u32);
+    }
+
+    Ok(())
+}

+ 748 - 0
src/sema/expression/member_access.rs

@@ -0,0 +1,748 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{ArrayLength, Builtin, Expression, Namespace, RetrieveType, Symbol, Type};
+use crate::sema::builtin;
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::constructor::circular_reference;
+use crate::sema::expression::function_call::function_type;
+use crate::sema::expression::integers::bigint_to_expression;
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::{assigned_variable, used_variable};
+use num_bigint::{BigInt, Sign};
+use num_traits::{FromPrimitive, One, Zero};
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+use std::ops::{Shl, Sub};
+
+/// Resolve an member access expression
+pub(super) fn member_access(
+    loc: &pt::Loc,
+    e: &pt::Expression,
+    id: &pt::Identifier,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    // is it a builtin special variable like "block.timestamp"
+    if let pt::Expression::Variable(namespace) = e {
+        if let Some((builtin, ty)) =
+            builtin::builtin_var(loc, Some(&namespace.name), &id.name, ns, diagnostics)
+        {
+            return Ok(Expression::Builtin {
+                loc: *loc,
+                tys: vec![ty],
+                kind: builtin,
+                args: vec![],
+            });
+        }
+
+        if builtin::builtin_namespace(&namespace.name) {
+            diagnostics.push(Diagnostic::error(
+                e.loc(),
+                format!("builtin '{}.{}' does not exist", namespace.name, id.name),
+            ));
+
+            return Err(());
+        }
+    }
+
+    // is it an enum value
+    if let Some(expr) = enum_value(
+        loc,
+        e,
+        id,
+        context.file_no,
+        context.contract_no,
+        ns,
+        diagnostics,
+    )? {
+        return Ok(expr);
+    }
+
+    // is it a constant (unless basecontract is a local variable)
+    if let Some(expr) = contract_constant(
+        loc,
+        e,
+        id,
+        context.file_no,
+        ns,
+        symtable,
+        diagnostics,
+        resolve_to,
+    )? {
+        return Ok(expr);
+    }
+
+    // is it a basecontract.function.selector expression (unless basecontract is a local variable)
+    if let pt::Expression::Variable(namespace) = e {
+        if symtable.find(&namespace.name).is_none() {
+            if let Some(call_contract_no) = ns.resolve_contract(context.file_no, namespace) {
+                // find function with this name
+                let mut name_matches = 0;
+                let mut expr = Err(());
+
+                for function_no in ns.contracts[call_contract_no].all_functions.keys() {
+                    let func = &ns.functions[*function_no];
+
+                    if func.name != id.name || func.ty != pt::FunctionTy::Function {
+                        continue;
+                    }
+
+                    name_matches += 1;
+
+                    expr = Ok(Expression::InternalFunction {
+                        loc: e.loc(),
+                        ty: function_type(func, false, resolve_to),
+                        function_no: *function_no,
+                        signature: None,
+                    })
+                }
+
+                return match name_matches {
+                    0 => {
+                        diagnostics.push(Diagnostic::error(
+                            e.loc(),
+                            format!(
+                                "contract '{}' does not have a member called '{}'",
+                                ns.contracts[call_contract_no].name, id.name,
+                            ),
+                        ));
+                        Err(())
+                    }
+                    1 => expr,
+                    _ => {
+                        diagnostics.push(Diagnostic::error(
+                            e.loc(),
+                            format!(
+                                "function '{}' of contract '{}' is overloaded",
+                                id.name, ns.contracts[call_contract_no].name,
+                            ),
+                        ));
+                        Err(())
+                    }
+                };
+            }
+        }
+    }
+
+    // is of the form "type(x).field", like type(c).min
+    if let pt::Expression::FunctionCall(_, name, args) = e {
+        if let pt::Expression::Variable(func_name) = name.as_ref() {
+            if func_name.name == "type" {
+                return type_name_expr(loc, args, id, context, ns, diagnostics, resolve_to);
+            }
+        }
+    }
+
+    let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+    let expr_ty = expr.ty();
+
+    if let Type::Struct(struct_ty) = expr_ty.deref_memory() {
+        if let Some((i, f)) = struct_ty
+            .definition(ns)
+            .fields
+            .iter()
+            .enumerate()
+            .find(|f| id.name == f.1.name_as_str())
+        {
+            return if context.lvalue && f.readonly {
+                diagnostics.push(Diagnostic::error(
+                    id.loc,
+                    format!(
+                        "struct '{}' field '{}' is readonly",
+                        struct_ty.definition(ns),
+                        id.name
+                    ),
+                ));
+                Err(())
+            } else if f.readonly {
+                // readonly fields return the value, not a reference
+                Ok(Expression::StructMember {
+                    loc: id.loc,
+                    ty: f.ty.clone(),
+                    expr: Box::new(expr),
+                    field: i,
+                })
+            } else {
+                Ok(Expression::StructMember {
+                    loc: id.loc,
+                    ty: Type::Ref(Box::new(f.ty.clone())),
+                    expr: Box::new(expr),
+                    field: i,
+                })
+            };
+        } else {
+            diagnostics.push(Diagnostic::error(
+                id.loc,
+                format!(
+                    "struct '{}' does not have a field called '{}'",
+                    struct_ty.definition(ns),
+                    id.name
+                ),
+            ));
+            return Err(());
+        }
+    }
+
+    // Dereference if need to
+    let (expr, expr_ty) = if let Type::Ref(ty) = &expr_ty {
+        (
+            Expression::Load {
+                loc: *loc,
+                ty: expr_ty.clone(),
+                expr: Box::new(expr),
+            },
+            ty.as_ref().clone(),
+        )
+    } else {
+        (expr, expr_ty)
+    };
+
+    match expr_ty {
+        Type::Bytes(n) => {
+            if id.name == "length" {
+                //We should not eliminate an array from the code when 'length' is called
+                //So the variable is also assigned a value to be read from 'length'
+                assigned_variable(ns, &expr, symtable);
+                used_variable(ns, &expr, symtable);
+                return Ok(Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: Type::Uint(8),
+                    value: BigInt::from_u8(n).unwrap(),
+                });
+            }
+        }
+        Type::Array(_, dim) => {
+            if id.name == "length" {
+                return match dim.last().unwrap() {
+                    ArrayLength::Dynamic => Ok(Expression::Builtin {
+                        loc: *loc,
+                        tys: vec![Type::Uint(32)],
+                        kind: Builtin::ArrayLength,
+                        args: vec![expr],
+                    }),
+                    ArrayLength::Fixed(d) => {
+                        //We should not eliminate an array from the code when 'length' is called
+                        //So the variable is also assigned a value to be read from 'length'
+                        assigned_variable(ns, &expr, symtable);
+                        used_variable(ns, &expr, symtable);
+                        bigint_to_expression(
+                            loc,
+                            d,
+                            ns,
+                            diagnostics,
+                            ResolveTo::Type(&Type::Uint(32)),
+                        )
+                    }
+                    ArrayLength::AnyFixed => unreachable!(),
+                };
+            }
+        }
+        Type::String | Type::DynamicBytes => {
+            if id.name == "length" {
+                return Ok(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![Type::Uint(32)],
+                    kind: Builtin::ArrayLength,
+                    args: vec![expr],
+                });
+            }
+        }
+        Type::StorageRef(immutable, r) => match *r {
+            Type::Struct(str_ty) => {
+                return if let Some((field_no, field)) = str_ty
+                    .definition(ns)
+                    .fields
+                    .iter()
+                    .enumerate()
+                    .find(|(_, field)| id.name == field.name_as_str())
+                {
+                    Ok(Expression::StructMember {
+                        loc: id.loc,
+                        ty: Type::StorageRef(immutable, Box::new(field.ty.clone())),
+                        expr: Box::new(expr),
+                        field: field_no,
+                    })
+                } else {
+                    diagnostics.push(Diagnostic::error(
+                        id.loc,
+                        format!(
+                            "struct '{}' does not have a field called '{}'",
+                            str_ty.definition(ns).name,
+                            id.name
+                        ),
+                    ));
+                    Err(())
+                }
+            }
+            Type::Array(_, dim) => {
+                if id.name == "length" {
+                    let elem_ty = expr.ty().storage_array_elem().deref_into();
+
+                    if let Some(ArrayLength::Fixed(dim)) = dim.last() {
+                        // sparse array could be large than ns.storage_type() on Solana
+                        if dim.bits() > ns.storage_type().bits(ns) as u64 {
+                            return Ok(Expression::StorageArrayLength {
+                                loc: id.loc,
+                                ty: Type::Uint(256),
+                                array: Box::new(expr),
+                                elem_ty,
+                            });
+                        }
+                    }
+
+                    return Ok(Expression::StorageArrayLength {
+                        loc: id.loc,
+                        ty: ns.storage_type(),
+                        array: Box::new(expr),
+                        elem_ty,
+                    });
+                }
+            }
+            Type::Bytes(_) | Type::DynamicBytes | Type::String => {
+                if id.name == "length" {
+                    let elem_ty = expr.ty().storage_array_elem().deref_into();
+
+                    return Ok(Expression::StorageArrayLength {
+                        loc: id.loc,
+                        ty: Type::Uint(32),
+                        array: Box::new(expr),
+                        elem_ty,
+                    });
+                }
+            }
+            _ => {}
+        },
+        Type::Address(_) => {
+            if id.name == "balance" {
+                if ns.target.is_substrate() {
+                    let mut is_this = false;
+
+                    if let Expression::Cast { expr: this, .. } = &expr {
+                        if let Expression::Builtin {
+                            kind: Builtin::GetAddress,
+                            ..
+                        } = this.as_ref()
+                        {
+                            is_this = true;
+                        }
+                    }
+
+                    if !is_this {
+                        diagnostics.push(Diagnostic::error(
+                            expr.loc(),
+                            "substrate can only retrieve balance of this, like 'address(this).balance'".to_string(),
+                        ));
+                        return Err(());
+                    }
+                }
+                used_variable(ns, &expr, symtable);
+                return Ok(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![Type::Value],
+                    kind: Builtin::Balance,
+                    args: vec![expr],
+                });
+            }
+        }
+        Type::Contract(ref_contract_no) => {
+            let mut name_matches = 0;
+            let mut ext_expr = Err(());
+
+            for function_no in ns.contracts[ref_contract_no].all_functions.keys() {
+                let func = &ns.functions[*function_no];
+
+                if func.name != id.name || func.ty != pt::FunctionTy::Function || !func.is_public()
+                {
+                    continue;
+                }
+
+                let ty = Type::ExternalFunction {
+                    params: func.params.iter().map(|p| p.ty.clone()).collect(),
+                    mutability: func.mutability.clone(),
+                    returns: func.returns.iter().map(|p| p.ty.clone()).collect(),
+                };
+
+                name_matches += 1;
+                ext_expr = Ok(Expression::ExternalFunction {
+                    loc: id.loc,
+                    ty,
+                    address: Box::new(expr.clone()),
+                    function_no: *function_no,
+                });
+            }
+
+            return match name_matches {
+                0 => {
+                    diagnostics.push(Diagnostic::error(
+                        id.loc,
+                        format!(
+                            "{} '{}' has no public function '{}'",
+                            ns.contracts[ref_contract_no].ty,
+                            ns.contracts[ref_contract_no].name,
+                            id.name
+                        ),
+                    ));
+                    Err(())
+                }
+                1 => ext_expr,
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        id.loc,
+                        format!(
+                            "function '{}' of {} '{}' is overloaded",
+                            id.name,
+                            ns.contracts[ref_contract_no].ty,
+                            ns.contracts[ref_contract_no].name
+                        ),
+                    ));
+                    Err(())
+                }
+            };
+        }
+        Type::ExternalFunction { .. } => {
+            if id.name == "address" {
+                used_variable(ns, &expr, symtable);
+                return Ok(Expression::Builtin {
+                    loc: e.loc(),
+                    tys: vec![Type::Address(false)],
+                    kind: Builtin::ExternalFunctionAddress,
+                    args: vec![expr],
+                });
+            }
+            if id.name == "selector" {
+                used_variable(ns, &expr, symtable);
+                return Ok(Expression::Builtin {
+                    loc: e.loc(),
+                    tys: vec![Type::FunctionSelector],
+                    kind: Builtin::FunctionSelector,
+                    args: vec![expr],
+                });
+            }
+        }
+        Type::InternalFunction { .. } => {
+            if let Expression::InternalFunction { .. } = expr {
+                if id.name == "selector" {
+                    used_variable(ns, &expr, symtable);
+                    return Ok(Expression::Builtin {
+                        loc: e.loc(),
+                        tys: vec![Type::FunctionSelector],
+                        kind: Builtin::FunctionSelector,
+                        args: vec![expr],
+                    });
+                }
+            }
+        }
+        _ => (),
+    }
+
+    diagnostics.push(Diagnostic::error(*loc, format!("'{}' not found", id.name)));
+
+    Err(())
+}
+
+fn contract_constant(
+    loc: &pt::Loc,
+    e: &pt::Expression,
+    id: &pt::Identifier,
+    file_no: usize,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Option<Expression>, ()> {
+    let namespace = match e {
+        pt::Expression::Variable(namespace) => namespace,
+        _ => return Ok(None),
+    };
+
+    if symtable.find(&namespace.name).is_some() {
+        return Ok(None);
+    }
+
+    if let Some(contract_no) = ns.resolve_contract(file_no, namespace) {
+        if let Some((var_no, var)) = ns.contracts[contract_no]
+            .variables
+            .iter_mut()
+            .enumerate()
+            .find(|(_, variable)| variable.name == id.name)
+        {
+            if !var.constant {
+                let resolve_function = if let ResolveTo::Type(ty) = resolve_to {
+                    matches!(
+                        ty,
+                        Type::InternalFunction { .. } | Type::ExternalFunction { .. }
+                    )
+                } else {
+                    false
+                };
+
+                if resolve_function {
+                    // requested function, fall through
+                    return Ok(None);
+                } else {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "need instance of contract '{}' to get variable value '{}'",
+                            ns.contracts[contract_no].name,
+                            ns.contracts[contract_no].variables[var_no].name,
+                        ),
+                    ));
+                    return Err(());
+                }
+            }
+
+            var.read = true;
+
+            return Ok(Some(Expression::ConstantVariable {
+                loc: *loc,
+                ty: var.ty.clone(),
+                contract_no: Some(contract_no),
+                var_no,
+            }));
+        }
+    }
+
+    Ok(None)
+}
+
+/// Try to resolve expression as an enum value. An enum can be prefixed
+/// with import symbols, contract namespace before the enum type
+fn enum_value(
+    loc: &pt::Loc,
+    expr: &pt::Expression,
+    id: &pt::Identifier,
+    file_no: usize,
+    contract_no: Option<usize>,
+    ns: &Namespace,
+    diagnostics: &mut Diagnostics,
+) -> Result<Option<Expression>, ()> {
+    let mut namespace = Vec::new();
+
+    let mut expr = expr;
+
+    // the first element of the path is the deepest in the parse tree,
+    // so walk down and add to a list
+    while let pt::Expression::MemberAccess(_, member, name) = expr {
+        namespace.push(name);
+
+        expr = member.as_ref();
+    }
+
+    if let pt::Expression::Variable(name) = expr {
+        namespace.push(name);
+    } else {
+        return Ok(None);
+    }
+
+    // The leading part of the namespace can be import variables
+    let mut file_no = file_no;
+
+    // last element in our namespace vector is first element
+    while let Some(name) = namespace.last().map(|f| f.name.clone()) {
+        if let Some(Symbol::Import(_, import_file_no)) =
+            ns.variable_symbols.get(&(file_no, None, name))
+        {
+            file_no = *import_file_no;
+            namespace.pop();
+        } else {
+            break;
+        }
+    }
+
+    if namespace.is_empty() {
+        return Ok(None);
+    }
+
+    let mut contract_no = contract_no;
+
+    if let Some(no) = ns.resolve_contract(file_no, namespace.last().unwrap()) {
+        contract_no = Some(no);
+        namespace.pop();
+    }
+
+    if namespace.len() != 1 {
+        return Ok(None);
+    }
+
+    if let Some(e) = ns.resolve_enum(file_no, contract_no, namespace[0]) {
+        match ns.enums[e].values.get_full(&id.name) {
+            Some((val, _, _)) => Ok(Some(Expression::NumberLiteral {
+                loc: *loc,
+                ty: Type::Enum(e),
+                value: BigInt::from_usize(val).unwrap(),
+            })),
+            None => {
+                diagnostics.push(Diagnostic::error(
+                    id.loc,
+                    format!("enum {} does not have value {}", ns.enums[e], id.name),
+                ));
+                Err(())
+            }
+        }
+    } else {
+        Ok(None)
+    }
+}
+
+/// Resolve type(x).foo
+fn type_name_expr(
+    loc: &pt::Loc,
+    args: &[pt::Expression],
+    field: &pt::Identifier,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    if args.is_empty() {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            "missing argument to type()".to_string(),
+        ));
+        return Err(());
+    }
+
+    if args.len() > 1 {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!("got {} arguments to type(), only one expected", args.len(),),
+        ));
+        return Err(());
+    }
+
+    let ty = ns.resolve_type(
+        context.file_no,
+        context.contract_no,
+        false,
+        &args[0],
+        diagnostics,
+    )?;
+
+    match (&ty, field.name.as_str()) {
+        (Type::Uint(_), "min") => {
+            bigint_to_expression(loc, &BigInt::zero(), ns, diagnostics, resolve_to)
+        }
+        (Type::Uint(bits), "max") => {
+            let max = BigInt::one().shl(*bits as usize).sub(1);
+            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to)
+        }
+        (Type::Int(bits), "min") => {
+            let min = BigInt::zero().sub(BigInt::one().shl(*bits as usize - 1));
+            bigint_to_expression(loc, &min, ns, diagnostics, resolve_to)
+        }
+        (Type::Int(bits), "max") => {
+            let max = BigInt::one().shl(*bits as usize - 1).sub(1);
+            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to)
+        }
+        (Type::Contract(n), "name") => Ok(Expression::BytesLiteral {
+            loc: *loc,
+            ty: Type::String,
+            value: ns.contracts[*n].name.as_bytes().to_vec(),
+        }),
+        (Type::Contract(n), "interfaceId") => {
+            let contract = &ns.contracts[*n];
+
+            if !contract.is_interface() {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "type(…).interfaceId is permitted on interface, not {} {}",
+                        contract.ty, contract.name
+                    ),
+                ));
+                Err(())
+            } else {
+                Ok(Expression::InterfaceId {
+                    loc: *loc,
+                    contract_no: *n,
+                })
+            }
+        }
+        (Type::Contract(no), "program_id") => {
+            let contract = &ns.contracts[*no];
+
+            if let Some(v) = &contract.program_id {
+                Ok(Expression::NumberLiteral {
+                    loc: *loc,
+                    ty: Type::Address(false),
+                    value: BigInt::from_bytes_be(Sign::Plus, v),
+                })
+            } else {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "{} '{}' has no declared program_id",
+                        contract.ty, contract.name
+                    ),
+                ));
+                Err(())
+            }
+        }
+        (Type::Contract(no), "creationCode") | (Type::Contract(no), "runtimeCode") => {
+            let contract_no = match context.contract_no {
+                Some(contract_no) => contract_no,
+                None => {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "type().{} not permitted outside of contract code",
+                            field.name
+                        ),
+                    ));
+                    return Err(());
+                }
+            };
+
+            // check for circular references
+            if *no == contract_no {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "containing our own contract code for '{}' would generate infinite size contract",
+                        ns.contracts[*no].name
+                    ),
+                ));
+                return Err(());
+            }
+
+            if circular_reference(*no, contract_no, ns) {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "circular reference creating contract code for '{}'",
+                        ns.contracts[*no].name
+                    ),
+                ));
+                return Err(());
+            }
+
+            if !ns.contracts[contract_no].creates.contains(no) {
+                ns.contracts[contract_no].creates.push(*no);
+            }
+
+            Ok(Expression::CodeLiteral {
+                loc: *loc,
+                contract_no: *no,
+                runtime: field.name == "runtimeCode",
+            })
+        }
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "type '{}' does not have type function {}",
+                    ty.to_string(ns),
+                    field.name
+                ),
+            ));
+            Err(())
+        }
+    }
+}

+ 1256 - 0
src/sema/expression/mod.rs

@@ -0,0 +1,1256 @@
+// SPDX-License-Identifier: Apache-2.0
+
+mod arithmetic;
+mod assign;
+pub(crate) mod constructor;
+pub(crate) mod function_call;
+pub(crate) mod integers;
+pub(crate) mod literals;
+mod member_access;
+pub mod resolve_expression;
+pub mod retrieve_type;
+pub(crate) mod strings;
+mod subscript;
+mod tests;
+mod variable;
+
+use super::ast::{ArrayLength, Diagnostic, Expression, Mutability, Namespace, RetrieveType, Type};
+use super::diagnostics::Diagnostics;
+use super::eval::eval_const_rational;
+use crate::sema::contracts::is_base;
+use crate::sema::eval::eval_const_number;
+use num_bigint::{BigInt, Sign};
+use num_rational::BigRational;
+use num_traits::{FromPrimitive, ToPrimitive, Zero};
+use solang_parser::pt::{self, CodeLocation};
+use std::cmp::Ordering;
+
+/// Compare two mutability levels
+pub fn compatible_mutability(left: &Mutability, right: &Mutability) -> bool {
+    matches!(
+        (left, right),
+        // only payable is compatible with payable
+        (Mutability::Payable(_), Mutability::Payable(_))
+            // default is compatible with anything but pure and view
+            | (Mutability::Nonpayable(_), Mutability::Nonpayable(_) | Mutability::Payable(_))
+            // view is compatible with anything but pure
+            | (Mutability::View(_), Mutability::View(_) | Mutability::Nonpayable(_) | Mutability::Payable(_))
+            // pure is compatible with anything
+            | (Mutability::Pure(_), _) // everything else is not compatible
+    )
+}
+
+/// When resolving an expression, what type are we looking for
+#[derive(PartialEq, Eq, Clone, Copy, Debug)]
+pub enum ResolveTo<'a> {
+    Unknown,        // We don't know what we're looking for, best effort
+    Integer,        // Try to resolve to an integer type value (signed or unsigned, any bit width)
+    Discard,        // We won't be using the result. For example, an expression as a statement
+    Type(&'a Type), // We will be wanting this type please, e.g. `int64 x = 1;`
+}
+
+#[derive(Clone, Default)]
+pub struct ExprContext {
+    /// What source file are we in
+    pub file_no: usize,
+    // Are we resolving a contract, and if so, which one
+    pub contract_no: Option<usize>,
+    /// Are resolving the body of a function, and if so, which one
+    pub function_no: Option<usize>,
+    /// Are we currently in an unchecked block
+    pub unchecked: bool,
+    /// Are we evaluating a constant expression
+    pub constant: bool,
+    /// Are we resolving an l-value
+    pub lvalue: bool,
+    /// Are we resolving a yul function (it cannot have external dependencies)
+    pub yul_function: bool,
+}
+
+impl Expression {
+    /// Is this expression 0
+    pub(super) fn const_zero(&self, ns: &Namespace) -> bool {
+        if let Ok((_, value)) = eval_const_number(self, ns) {
+            value == BigInt::zero()
+        } else {
+            false
+        }
+    }
+
+    /// Return the type for this expression.
+    pub fn tys(&self) -> Vec<Type> {
+        match self {
+            Expression::Builtin { tys: returns, .. }
+            | Expression::InternalFunctionCall { returns, .. }
+            | Expression::ExternalFunctionCall { returns, .. } => returns.to_vec(),
+            Expression::List { list, .. } => list.iter().map(|e| e.ty()).collect(),
+            Expression::ExternalFunctionCallRaw { .. } => vec![Type::Bool, Type::DynamicBytes],
+            _ => vec![self.ty()],
+        }
+    }
+
+    /// Cast from one type to another, which also automatically derefs any Type::Ref() type.
+    /// if the cast is explicit (e.g. bytes32(bar) then implicit should be set to false.
+    pub(crate) fn cast(
+        &self,
+        loc: &pt::Loc,
+        to: &Type,
+        implicit: bool,
+        ns: &Namespace,
+        diagnostics: &mut Diagnostics,
+    ) -> Result<Expression, ()> {
+        let from = self.ty();
+        if &from == to {
+            return Ok(self.clone());
+        }
+
+        if from == Type::Unresolved || *to == Type::Unresolved {
+            return Ok(self.clone());
+        }
+
+        // First of all, if we have a ref then derefence it
+        if let Type::Ref(r) = &from {
+            return if r.is_fixed_reference_type() {
+                // A struct/fixed array *value* is simply the type, e.g. Type::Struct(_)
+                // An assignable struct value, e.g. member of another struct, is Type::Ref(Type:Struct(_)).
+                // However, the underlying types are identical: simply a pointer.
+                //
+                // So a Type::Ref(Type::Struct(_)) can be cast to Type::Struct(_).
+                //
+                // The Type::Ref(..) just means it can be used as an l-value and assigned
+                // a new value, unlike say, a struct literal.
+                if r.as_ref() == to {
+                    Ok(self.clone())
+                } else {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "conversion from {} to {} not possible",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                }
+            } else {
+                Expression::Load {
+                    loc: *loc,
+                    ty: r.as_ref().clone(),
+                    expr: Box::new(self.clone()),
+                }
+                .cast(loc, to, implicit, ns, diagnostics)
+            };
+        }
+
+        // If it's a storage reference then load the value. The expr is the storage slot
+        if let Type::StorageRef(_, r) = from {
+            if let Expression::Subscript { array_ty: ty, .. } = self {
+                if ty.is_storage_bytes() {
+                    return Ok(self.clone());
+                }
+            }
+
+            return Expression::StorageLoad {
+                loc: *loc,
+                ty: *r,
+                expr: Box::new(self.clone()),
+            }
+            .cast(loc, to, implicit, ns, diagnostics);
+        }
+
+        // Special case: when converting literal sign can change if it fits
+        match (self, &from, to) {
+            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Uint(to_len))
+                if p.is_primitive() =>
+            {
+                return if value.sign() == Sign::Minus {
+                    if implicit {
+                        diagnostics.push(Diagnostic::cast_error(
+                            *loc,
+                            format!(
+                                "implicit conversion cannot change negative number to '{}'",
+                                to.to_string(ns)
+                            ),
+                        ));
+                        Err(())
+                    } else {
+                        // Convert to little endian so most significant bytes are at the end; that way
+                        // we can simply resize the vector to the right size
+                        let mut bs = value.to_signed_bytes_le();
+
+                        bs.resize(to_len as usize / 8, 0xff);
+                        Ok(Expression::NumberLiteral {
+                            loc: *loc,
+                            ty: Type::Uint(to_len),
+                            value: BigInt::from_bytes_le(Sign::Plus, &bs),
+                        })
+                    }
+                } else if value.bits() >= to_len as u64 {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would truncate from '{}' to '{}'",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::NumberLiteral {
+                        loc: *loc,
+                        ty: Type::Uint(to_len),
+                        value: value.clone(),
+                    })
+                };
+            }
+            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Int(to_len))
+                if p.is_primitive() =>
+            {
+                return if value.bits() >= to_len as u64 {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would truncate from '{}' to '{}'",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::NumberLiteral {
+                        loc: *loc,
+                        ty: Type::Int(to_len),
+                        value: value.clone(),
+                    })
+                };
+            }
+            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Bytes(to_len))
+                if p.is_primitive() =>
+            {
+                // round up the number of bits to bytes
+                let bytes = (value.bits() + 7) / 8;
+                return if value.sign() == Sign::Minus {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "negative number cannot be converted to type '{}'",
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if value.sign() == Sign::Plus && bytes != to_len as u64 {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "number of {} bytes cannot be converted to type '{}'",
+                            bytes,
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::NumberLiteral {
+                        loc: *loc,
+                        ty: Type::Bytes(to_len),
+                        value: value.clone(),
+                    })
+                };
+            }
+            (&Expression::NumberLiteral { ref value, .. }, p, &Type::Address(payable))
+                if p.is_primitive() =>
+            {
+                // note: negative values are allowed
+                return if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        String::from("implicit conversion from int to address not allowed"),
+                    ));
+                    Err(())
+                } else if value.bits() > ns.address_length as u64 * 8 {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "number larger than possible in {} byte address",
+                            ns.address_length,
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::NumberLiteral {
+                        loc: *loc,
+                        ty: Type::Address(payable),
+                        value: value.clone(),
+                    })
+                };
+            }
+            // Literal strings can be implicitly lengthened
+            (&Expression::BytesLiteral { ref value, .. }, p, &Type::Bytes(to_len))
+                if p.is_primitive() =>
+            {
+                return if value.len() > to_len as usize && implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would truncate from '{}' to '{}'",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    let mut bs = value.to_owned();
+
+                    // Add zero's at the end as needed
+                    bs.resize(to_len as usize, 0);
+
+                    Ok(Expression::BytesLiteral {
+                        loc: *loc,
+                        ty: Type::Bytes(to_len),
+                        value: bs,
+                    })
+                };
+            }
+            (&Expression::BytesLiteral { loc, ref value, .. }, _, &Type::DynamicBytes)
+            | (&Expression::BytesLiteral { loc, ref value, .. }, _, &Type::String) => {
+                return Ok(Expression::AllocDynamicBytes {
+                    loc,
+                    ty: to.clone(),
+                    length: Box::new(Expression::NumberLiteral {
+                        loc,
+                        ty: Type::Uint(32),
+                        value: BigInt::from(value.len()),
+                    }),
+                    init: Some(value.clone()),
+                });
+            }
+            (&Expression::NumberLiteral { ref value, .. }, _, &Type::Rational) => {
+                return Ok(Expression::RationalNumberLiteral {
+                    loc: *loc,
+                    ty: Type::Rational,
+                    value: BigRational::from(value.clone()),
+                });
+            }
+
+            (
+                &Expression::ArrayLiteral { .. },
+                Type::Array(from_ty, from_dims),
+                Type::Array(to_ty, to_dims),
+            ) => {
+                if from_ty == to_ty
+                    && from_dims.len() == to_dims.len()
+                    && from_dims.len() == 1
+                    && matches!(to_dims.last().unwrap(), ArrayLength::Dynamic)
+                {
+                    return Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    });
+                }
+            }
+
+            _ => (),
+        };
+
+        self.cast_types(loc, &from, to, implicit, ns, diagnostics)
+    }
+
+    fn cast_types(
+        &self,
+        loc: &pt::Loc,
+        from: &Type,
+        to: &Type,
+        implicit: bool,
+        ns: &Namespace,
+        diagnostics: &mut Diagnostics,
+    ) -> Result<Expression, ()> {
+        let address_bits = ns.address_length as u16 * 8;
+
+        match (&from, &to) {
+            // Solana builtin AccountMeta struct wants a pointer to an address for the pubkey field,
+            // not an address. For this specific field we have a special Expression::GetRef() which
+            // gets the pointer to an address
+            (Type::Address(_), Type::Ref(to)) if matches!(to.as_ref(), Type::Address(..)) => {
+                Ok(Expression::GetRef {
+                    loc: *loc,
+                    ty: Type::Ref(Box::new(from.clone())),
+                    expr: Box::new(self.clone()),
+                })
+            }
+            (Type::Uint(from_width), Type::Enum(enum_no))
+            | (Type::Int(from_width), Type::Enum(enum_no)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion from {} to {} not allowed",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    return Err(());
+                }
+
+                let enum_ty = &ns.enums[*enum_no];
+
+                // TODO would be help to have current contract to resolve contract constants
+                if let Ok((_, big_number)) = eval_const_number(self, ns) {
+                    if let Some(number) = big_number.to_usize() {
+                        if enum_ty.values.len() > number {
+                            return Ok(Expression::NumberLiteral {
+                                loc: self.loc(),
+                                ty: to.clone(),
+                                value: big_number,
+                            });
+                        }
+                    }
+
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "enum {} has no value with ordinal {}",
+                            to.to_string(ns),
+                            big_number
+                        ),
+                    ));
+                    return Err(());
+                }
+
+                let to_width = enum_ty.ty.bits(ns);
+
+                // TODO needs runtime checks
+                match from_width.cmp(&to_width) {
+                    Ordering::Greater => Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Less => Ok(Expression::ZeroExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Equal => Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                }
+            }
+            (Type::Enum(enum_no), Type::Uint(to_width))
+            | (Type::Enum(enum_no), Type::Int(to_width)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion from {} to {} not allowed",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    return Err(());
+                }
+                let enum_ty = &ns.enums[*enum_no];
+                let from_width = enum_ty.ty.bits(ns);
+
+                match from_width.cmp(to_width) {
+                    Ordering::Greater => Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Less => Ok(Expression::ZeroExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Equal => Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                }
+            }
+            (Type::Bytes(n), Type::FunctionSelector) if *n == ns.target.selector_length() => {
+                Ok(Expression::Cast {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                })
+            }
+
+            (Type::Bytes(1), Type::Uint(8)) | (Type::Uint(8), Type::Bytes(1)) => Ok(self.clone()),
+            (Type::Uint(from_len), Type::Uint(to_len)) => match from_len.cmp(to_len) {
+                Ordering::Greater => {
+                    if implicit {
+                        diagnostics.push(Diagnostic::cast_error(
+                            *loc,
+                            format!(
+                                "implicit conversion would truncate from {} to {}",
+                                from.to_string(ns),
+                                to.to_string(ns)
+                            ),
+                        ));
+                        Err(())
+                    } else {
+                        Ok(Expression::Trunc {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(self.clone()),
+                        })
+                    }
+                }
+                Ordering::Less => Ok(Expression::ZeroExt {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                }),
+                Ordering::Equal => Ok(Expression::Cast {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                }),
+            },
+            (Type::Int(from_len), Type::Int(to_len)) => match from_len.cmp(to_len) {
+                Ordering::Greater => {
+                    if implicit {
+                        diagnostics.push(Diagnostic::cast_error(
+                            *loc,
+                            format!(
+                                "implicit conversion would truncate from {} to {}",
+                                from.to_string(ns),
+                                to.to_string(ns)
+                            ),
+                        ));
+                        Err(())
+                    } else {
+                        Ok(Expression::Trunc {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(self.clone()),
+                        })
+                    }
+                }
+                Ordering::Less => Ok(Expression::SignExt {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                }),
+                Ordering::Equal => Ok(Expression::Cast {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                }),
+            },
+            (Type::Uint(from_len), Type::Int(to_len)) if to_len > from_len => {
+                Ok(Expression::ZeroExt {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                })
+            }
+            (Type::Int(from_len), Type::Uint(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would change sign from {} to {}",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if from_len > to_len {
+                    Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else if from_len < to_len {
+                    Ok(Expression::SignExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            (Type::Uint(from_len), Type::Int(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would change sign from {} to {}",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if from_len > to_len {
+                    Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else if from_len < to_len {
+                    Ok(Expression::ZeroExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Casting value to uint
+            (Type::Value, Type::Uint(to_len)) => {
+                let from_len = ns.value_length * 8;
+                let to_len = *to_len as usize;
+
+                match from_len.cmp(&to_len) {
+                    Ordering::Greater => {
+                        if implicit {
+                            diagnostics.push(Diagnostic::cast_error(
+                                *loc,
+                                format!(
+                                    "implicit conversion would truncate from {} to {}",
+                                    from.to_string(ns),
+                                    to.to_string(ns)
+                                ),
+                            ));
+                            Err(())
+                        } else {
+                            Ok(Expression::Trunc {
+                                loc: *loc,
+                                to: to.clone(),
+                                expr: Box::new(self.clone()),
+                            })
+                        }
+                    }
+                    Ordering::Less => Ok(Expression::SignExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Equal => Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                }
+            }
+            (Type::Value, Type::Int(to_len)) => {
+                let from_len = ns.value_length * 8;
+                let to_len = *to_len as usize;
+
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would change sign from {} to {}",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if from_len > to_len {
+                    Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else if from_len < to_len {
+                    Ok(Expression::ZeroExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Casting value to uint
+            (Type::Uint(from_len), Type::Value) => {
+                let from_len = *from_len as usize;
+                let to_len = ns.value_length * 8;
+
+                match from_len.cmp(&to_len) {
+                    Ordering::Greater => {
+                        diagnostics.push(Diagnostic::cast_warning(
+                            *loc,
+                            format!(
+                                "conversion truncates {} to {}, as value is type {} on target {}",
+                                from.to_string(ns),
+                                to.to_string(ns),
+                                Type::Value.to_string(ns),
+                                ns.target
+                            ),
+                        ));
+
+                        Ok(Expression::CheckingTrunc {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(self.clone()),
+                        })
+                    }
+                    Ordering::Less => Ok(Expression::SignExt {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                    Ordering::Equal => Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    }),
+                }
+            }
+            // Casting int to address
+            (Type::Uint(from_len), Type::Address(_)) | (Type::Int(from_len), Type::Address(_)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion from {} to address not allowed",
+                            from.to_string(ns)
+                        ),
+                    ));
+
+                    Err(())
+                } else {
+                    // cast integer it to integer of the same size of address with sign ext etc
+                    let address_to_int = if from.is_signed_int() {
+                        Type::Int(address_bits)
+                    } else {
+                        Type::Uint(address_bits)
+                    };
+
+                    let expr = match from_len.cmp(&address_bits) {
+                        Ordering::Greater => Expression::Trunc {
+                            loc: *loc,
+                            to: address_to_int,
+                            expr: Box::new(self.clone()),
+                        },
+                        Ordering::Less if from.is_signed_int() => Expression::ZeroExt {
+                            loc: *loc,
+                            to: address_to_int,
+                            expr: Box::new(self.clone()),
+                        },
+                        Ordering::Less => Expression::SignExt {
+                            loc: *loc,
+                            to: address_to_int,
+                            expr: Box::new(self.clone()),
+                        },
+                        Ordering::Equal => self.clone(),
+                    };
+
+                    // Now cast integer to address
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(expr),
+                    })
+                }
+            }
+            // Casting address to int
+            (Type::Address(_), Type::Uint(to_len)) | (Type::Address(_), Type::Int(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+
+                    Err(())
+                } else {
+                    // first convert address to int/uint
+                    let address_to_int = if to.is_signed_int() {
+                        Type::Int(address_bits)
+                    } else {
+                        Type::Uint(address_bits)
+                    };
+
+                    let expr = Expression::Cast {
+                        loc: *loc,
+                        to: address_to_int,
+                        expr: Box::new(self.clone()),
+                    };
+                    // now resize int to request size with sign extension etc
+                    match to_len.cmp(&address_bits) {
+                        Ordering::Less => Ok(Expression::Trunc {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(expr),
+                        }),
+                        Ordering::Greater if to.is_signed_int() => Ok(Expression::ZeroExt {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(expr),
+                        }),
+                        Ordering::Greater => Ok(Expression::SignExt {
+                            loc: *loc,
+                            to: to.clone(),
+                            expr: Box::new(expr),
+                        }),
+                        Ordering::Equal => Ok(expr),
+                    }
+                }
+            }
+            // Lengthing or shorting a fixed bytes array
+            (Type::Bytes(from_len), Type::Bytes(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion would truncate from {} to {}",
+                            from.to_string(ns),
+                            to.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if to_len > from_len {
+                    let shift = (to_len - from_len) * 8;
+
+                    Ok(Expression::ShiftLeft {
+                        loc: *loc,
+                        ty: to.clone(),
+                        left: Box::new(Expression::ZeroExt {
+                            loc: self.loc(),
+                            to: to.clone(),
+                            expr: Box::new(self.clone()),
+                        }),
+                        right: Box::new(Expression::NumberLiteral {
+                            loc: *loc,
+                            ty: Type::Uint(*to_len as u16 * 8),
+                            value: BigInt::from_u8(shift).unwrap(),
+                        }),
+                    })
+                } else {
+                    let shift = (from_len - to_len) * 8;
+
+                    Ok(Expression::Trunc {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(Expression::ShiftRight {
+                            loc: self.loc(),
+                            ty: from.clone(),
+                            left: Box::new(self.clone()),
+                            right: Box::new(Expression::NumberLiteral {
+                                loc: self.loc(),
+                                ty: Type::Uint(*from_len as u16 * 8),
+                                value: BigInt::from_u8(shift).unwrap(),
+                            }),
+                            sign: false,
+                        }),
+                    })
+                }
+            }
+            (Type::Rational, Type::Uint(_) | Type::Int(_) | Type::Value) => {
+                match eval_const_rational(self, ns) {
+                    Ok((_, big_number)) => {
+                        if big_number.is_integer() {
+                            return Ok(Expression::Cast {
+                                loc: *loc,
+                                to: to.clone(),
+                                expr: Box::new(self.clone()),
+                            });
+                        }
+
+                        diagnostics.push(Diagnostic::cast_error(
+                            *loc,
+                            format!(
+                                "conversion to {} from {} not allowed",
+                                to.to_string(ns),
+                                from.to_string(ns)
+                            ),
+                        ));
+
+                        Err(())
+                    }
+                    Err(diag) => {
+                        diagnostics.push(diag);
+                        Err(())
+                    }
+                }
+            }
+            (Type::Uint(_) | Type::Int(_) | Type::Value, Type::Rational) => Ok(Expression::Cast {
+                loc: *loc,
+                to: to.clone(),
+                expr: Box::new(self.clone()),
+            }),
+            (Type::Bytes(_), Type::DynamicBytes) | (Type::DynamicBytes, Type::Bytes(_)) => {
+                Ok(Expression::BytesCast {
+                    loc: *loc,
+                    to: to.clone(),
+                    from: from.clone(),
+                    expr: Box::new(self.clone()),
+                })
+            }
+            // Explicit conversion from bytesN to int/uint only allowed with expliciy
+            // cast and if it is the same size (i.e. no conversion required)
+            (Type::Bytes(from_len), Type::Uint(to_len))
+            | (Type::Bytes(from_len), Type::Int(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if *from_len as u16 * 8 != *to_len {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Explicit conversion to bytesN from int/uint only allowed with expliciy
+            // cast and if it is the same size (i.e. no conversion required)
+            (Type::Uint(from_len), Type::Bytes(to_len))
+            | (Type::Int(from_len), Type::Bytes(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if *to_len as u16 * 8 != *from_len {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Explicit conversion from bytesN to address only allowed with expliciy
+            // cast and if it is the same size (i.e. no conversion required)
+            (Type::Bytes(from_len), Type::Address(_)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if *from_len as usize != ns.address_length {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Explicit conversion between contract and address is allowed
+            (Type::Address(false), Type::Address(true))
+            | (Type::Address(_), Type::Contract(_))
+            | (Type::Contract(_), Type::Address(_)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Conversion between contracts is allowed if it is a base
+            (Type::Contract(contract_no_from), Type::Contract(contract_no_to)) => {
+                if implicit && !is_base(*contract_no_to, *contract_no_from, ns) {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion not allowed since {} is not a base contract of {}",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // conversion from address payable to address is implicitly allowed (not vice versa)
+            (Type::Address(true), Type::Address(false)) => Ok(Expression::Cast {
+                loc: *loc,
+                to: to.clone(),
+                expr: Box::new(self.clone()),
+            }),
+            // Explicit conversion to bytesN from int/uint only allowed with expliciy
+            // cast and if it is the same size (i.e. no conversion required)
+            (Type::Address(_), Type::Bytes(to_len)) => {
+                if implicit {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "implicit conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if *to_len as usize != ns.address_length {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "conversion to {} from {} not allowed",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            (Type::String, Type::DynamicBytes) | (Type::DynamicBytes, Type::String)
+                if !implicit =>
+            {
+                Ok(Expression::Cast {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                })
+            }
+            // string conversions
+            // (Type::Bytes(_), Type::String) => Ok(Expression::Cast(self.loc(), to.clone(), Box::new(self.clone()))),
+            /*
+            (Type::String, Type::Bytes(to_len)) => {
+                if let Expression::BytesLiteral(_, from_str) = self {
+                    if from_str.len() > to_len as usize {
+                        diagnostics.push(Output::type_error(
+                            self.loc(),
+                            format!(
+                                "string of {} bytes is too long to fit into {}",
+                                from_str.len(),
+                                to.to_string(ns)
+                            ),
+                        ));
+                        return Err(());
+                    }
+                }
+                Ok(Expression::Cast(self.loc(), to.clone(), Box::new(self.clone()))
+            }
+            */
+            (Type::Void, _) => {
+                diagnostics.push(Diagnostic::cast_error(
+                    self.loc(),
+                    "function or method does not return a value".to_string(),
+                ));
+                Err(())
+            }
+            (
+                Type::ExternalFunction {
+                    params: from_params,
+                    mutability: from_mutablity,
+                    returns: from_returns,
+                },
+                Type::ExternalFunction {
+                    params: to_params,
+                    mutability: to_mutablity,
+                    returns: to_returns,
+                },
+            )
+            | (
+                Type::InternalFunction {
+                    params: from_params,
+                    mutability: from_mutablity,
+                    returns: from_returns,
+                },
+                Type::InternalFunction {
+                    params: to_params,
+                    mutability: to_mutablity,
+                    returns: to_returns,
+                },
+            ) => {
+                if from_params != to_params {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "function arguments do not match in conversion from '{}' to '{}'",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if from_returns != to_returns {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "function returns do not match in conversion from '{}' to '{}'",
+                            to.to_string(ns),
+                            from.to_string(ns)
+                        ),
+                    ));
+                    Err(())
+                } else if !compatible_mutability(from_mutablity, to_mutablity) {
+                    diagnostics.push(Diagnostic::cast_error(
+                        *loc,
+                        format!(
+                            "function mutability not compatible in conversion from '{}' to '{}'",
+                            from.to_string(ns),
+                            to.to_string(ns),
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: Box::new(self.clone()),
+                    })
+                }
+            }
+            // Match any array with ArrayLength::AnyFixed if is it fixed for that dimension, and the
+            // element type and other dimensions also match
+            (Type::Array(from_elem, from_dim), Type::Array(to_elem, to_dim))
+                if from_elem == to_elem
+                    && from_dim.len() == to_dim.len()
+                    && from_dim.iter().zip(to_dim.iter()).all(|(f, t)| {
+                        f == t || matches!((f, t), (ArrayLength::Fixed(_), ArrayLength::AnyFixed))
+                    }) =>
+            {
+                Ok(self.clone())
+            }
+            (Type::DynamicBytes, Type::Slice(ty)) if ty.as_ref() == &Type::Bytes(1) => {
+                Ok(Expression::Cast {
+                    loc: *loc,
+                    to: to.clone(),
+                    expr: Box::new(self.clone()),
+                })
+            }
+            (Type::FunctionSelector, Type::Bytes(n)) => {
+                let selector_length = ns.target.selector_length();
+                if *n == selector_length {
+                    Ok(Expression::Cast {
+                        loc: *loc,
+                        to: to.clone(),
+                        expr: self.clone().into(),
+                    })
+                } else {
+                    if *n < selector_length {
+                        diagnostics.push(Diagnostic::warning(
+                            *loc,
+                            format!(
+                                "function selector should only be casted to bytes{} or larger",
+                                selector_length
+                            ),
+                        ));
+                    }
+                    self.cast_types(
+                        loc,
+                        &Type::Bytes(selector_length),
+                        to,
+                        implicit,
+                        ns,
+                        diagnostics,
+                    )
+                }
+            }
+            (Type::FunctionSelector, Type::Uint(n) | Type::Int(n)) => {
+                let selector_width = ns.target.selector_length() * 8;
+                if *n < selector_width as u16 {
+                    diagnostics.push(Diagnostic::warning(
+                        *loc,
+                        format!(
+                            "function selector needs an integer of at least {} bits to avoid being truncated",
+                            selector_width
+                        ),
+                    ));
+                }
+                self.cast_types(
+                    loc,
+                    &Type::Bytes(ns.target.selector_length()),
+                    to,
+                    implicit,
+                    ns,
+                    diagnostics,
+                )
+            }
+            _ => {
+                diagnostics.push(Diagnostic::cast_error(
+                    *loc,
+                    format!(
+                        "conversion from {} to {} not possible",
+                        from.to_string(ns),
+                        to.to_string(ns)
+                    ),
+                ));
+                Err(())
+            }
+        }
+    }
+}

+ 669 - 0
src/sema/expression/resolve_expression.rs

@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Builtin, Expression, Namespace, RetrieveType, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::eval::{check_term_for_constant_overflow, eval_const_rational};
+use crate::sema::expression::arithmetic::{
+    addition, bitwise_and, bitwise_or, bitwise_xor, divide, equal, incr_decr, modulo, multiply,
+    power, shift_left, shift_right, subtract,
+};
+use crate::sema::expression::assign::{assign_expr, assign_single};
+use crate::sema::expression::constructor::{constructor_named_args, new};
+use crate::sema::expression::function_call::{call_expr, named_call_expr};
+use crate::sema::expression::integers::{
+    bigint_to_expression, coerce, coerce_number, get_int_length,
+};
+use crate::sema::expression::literals::{
+    address_literal, array_literal, hex_literal, hex_number_literal, number_literal,
+    rational_number_literal, string_literal,
+};
+use crate::sema::expression::member_access::member_access;
+use crate::sema::expression::subscript::array_subscript;
+use crate::sema::expression::variable::variable;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::unused_variable::{
+    check_function_call, check_var_usage_expression, used_variable,
+};
+use crate::sema::Recurse;
+use num_bigint::BigInt;
+use num_traits::{Num, One};
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+
+/// Resolve a parsed expression into an AST expression. The resolve_to argument is a hint to what
+/// type the result should be.
+pub fn expression(
+    expr: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    match expr {
+        pt::Expression::Parenthesis(_, expr) => {
+            expression(expr, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::ArrayLiteral(loc, exprs) => {
+            let res = array_literal(loc, exprs, context, ns, symtable, diagnostics, resolve_to);
+
+            if let Ok(exp) = &res {
+                used_variable(ns, exp, symtable);
+            }
+
+            res
+        }
+        pt::Expression::BoolLiteral(loc, v) => Ok(Expression::BoolLiteral {
+            loc: *loc,
+            value: *v,
+        }),
+        pt::Expression::StringLiteral(v) => {
+            Ok(string_literal(v, context.file_no, diagnostics, resolve_to))
+        }
+        pt::Expression::HexLiteral(v) => hex_literal(v, diagnostics, resolve_to),
+        pt::Expression::NumberLiteral(loc, integer, exp) => number_literal(
+            loc,
+            integer,
+            exp,
+            ns,
+            &BigInt::one(),
+            diagnostics,
+            resolve_to,
+        ),
+        pt::Expression::RationalNumberLiteral(loc, integer, fraction, exp) => {
+            rational_number_literal(
+                loc,
+                integer,
+                fraction,
+                exp,
+                &BigInt::one(),
+                ns,
+                diagnostics,
+                resolve_to,
+            )
+        }
+        pt::Expression::HexNumberLiteral(loc, n) => {
+            hex_number_literal(loc, n, ns, diagnostics, resolve_to)
+        }
+        pt::Expression::AddressLiteral(loc, address) => {
+            address_literal(loc, address, ns, diagnostics)
+        }
+        pt::Expression::Variable(id) => {
+            variable(id, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Add(loc, l, r) => {
+            addition(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Subtract(loc, l, r) => {
+            subtract(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::BitwiseOr(loc, l, r) => {
+            bitwise_or(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::BitwiseAnd(loc, l, r) => {
+            bitwise_and(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::BitwiseXor(loc, l, r) => {
+            bitwise_xor(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::ShiftLeft(loc, l, r) => {
+            shift_left(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::ShiftRight(loc, l, r) => {
+            shift_right(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Multiply(loc, l, r) => {
+            multiply(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Divide(loc, l, r) => {
+            divide(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Modulo(loc, l, r) => {
+            modulo(loc, l, r, context, ns, symtable, diagnostics, resolve_to)
+        }
+        pt::Expression::Power(loc, b, e) => {
+            power(loc, b, e, context, ns, symtable, diagnostics, resolve_to)
+        }
+        // compare
+        pt::Expression::More(loc, l, r) => {
+            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+
+            check_var_usage_expression(ns, &left, &right, symtable);
+            let ty = coerce_number(
+                &left.ty(),
+                &l.loc(),
+                &right.ty(),
+                &r.loc(),
+                true,
+                true,
+                ns,
+                diagnostics,
+            )?;
+
+            let expr = Expression::More {
+                loc: *loc,
+                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+            };
+
+            if ty.is_rational() {
+                if let Err(diag) = eval_const_rational(&expr, ns) {
+                    diagnostics.push(diag);
+                }
+            }
+
+            Ok(expr)
+        }
+        pt::Expression::Less(loc, l, r) => {
+            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+
+            check_var_usage_expression(ns, &left, &right, symtable);
+
+            let ty = coerce_number(
+                &left.ty(),
+                &l.loc(),
+                &right.ty(),
+                &r.loc(),
+                true,
+                true,
+                ns,
+                diagnostics,
+            )?;
+
+            let expr = Expression::Less {
+                loc: *loc,
+                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+            };
+
+            if ty.is_rational() {
+                if let Err(diag) = eval_const_rational(&expr, ns) {
+                    diagnostics.push(diag);
+                }
+            }
+
+            Ok(expr)
+        }
+        pt::Expression::MoreEqual(loc, l, r) => {
+            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            check_var_usage_expression(ns, &left, &right, symtable);
+
+            let ty = coerce_number(
+                &left.ty(),
+                &l.loc(),
+                &right.ty(),
+                &r.loc(),
+                true,
+                true,
+                ns,
+                diagnostics,
+            )?;
+
+            let expr = Expression::MoreEqual {
+                loc: *loc,
+                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+            };
+
+            if ty.is_rational() {
+                if let Err(diag) = eval_const_rational(&expr, ns) {
+                    diagnostics.push(diag);
+                }
+            }
+
+            Ok(expr)
+        }
+        pt::Expression::LessEqual(loc, l, r) => {
+            let left = expression(l, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            let right = expression(r, context, ns, symtable, diagnostics, ResolveTo::Integer)?;
+            check_var_usage_expression(ns, &left, &right, symtable);
+
+            let ty = coerce_number(
+                &left.ty(),
+                &l.loc(),
+                &right.ty(),
+                &r.loc(),
+                true,
+                true,
+                ns,
+                diagnostics,
+            )?;
+
+            let expr = Expression::LessEqual {
+                loc: *loc,
+                left: Box::new(left.cast(&l.loc(), &ty, true, ns, diagnostics)?),
+                right: Box::new(right.cast(&r.loc(), &ty, true, ns, diagnostics)?),
+            };
+
+            if ty.is_rational() {
+                if let Err(diag) = eval_const_rational(&expr, ns) {
+                    diagnostics.push(diag);
+                }
+            }
+
+            Ok(expr)
+        }
+        pt::Expression::Equal(loc, l, r) => equal(loc, l, r, context, ns, symtable, diagnostics),
+
+        pt::Expression::NotEqual(loc, l, r) => Ok(Expression::Not {
+            loc: *loc,
+            expr: Box::new(equal(loc, l, r, context, ns, symtable, diagnostics)?),
+        }),
+        // unary expressions
+        pt::Expression::Not(loc, e) => {
+            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+
+            used_variable(ns, &expr, symtable);
+            Ok(Expression::Not {
+                loc: *loc,
+                expr: Box::new(expr.cast(loc, &Type::Bool, true, ns, diagnostics)?),
+            })
+        }
+        pt::Expression::Complement(loc, e) => {
+            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+
+            used_variable(ns, &expr, symtable);
+            let expr_ty = expr.ty();
+
+            get_int_length(&expr_ty, loc, true, ns, diagnostics)?;
+
+            Ok(Expression::Complement {
+                loc: *loc,
+                ty: expr_ty,
+                expr: Box::new(expr),
+            })
+        }
+        pt::Expression::UnaryMinus(loc, e) => match e.as_ref() {
+            pt::Expression::NumberLiteral(_, integer, exp) => number_literal(
+                loc,
+                integer,
+                exp,
+                ns,
+                &BigInt::from(-1),
+                diagnostics,
+                resolve_to,
+            ),
+            pt::Expression::HexNumberLiteral(_, v) => {
+                // a hex literal with a minus before it cannot be an address literal or a bytesN value
+                let s: String = v.chars().skip(2).filter(|v| *v != '_').collect();
+
+                let n = BigInt::from_str_radix(&s, 16).unwrap();
+
+                bigint_to_expression(loc, &-n, ns, diagnostics, resolve_to)
+            }
+            pt::Expression::RationalNumberLiteral(loc, integer, fraction, exp) => {
+                rational_number_literal(
+                    loc,
+                    integer,
+                    fraction,
+                    exp,
+                    &BigInt::from(-1),
+                    ns,
+                    diagnostics,
+                    resolve_to,
+                )
+            }
+            e => {
+                let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+
+                used_variable(ns, &expr, symtable);
+                let expr_type = expr.ty();
+
+                if let Expression::NumberLiteral { value, .. } = expr {
+                    bigint_to_expression(loc, &-value, ns, diagnostics, resolve_to)
+                } else if let Expression::RationalNumberLiteral { ty, value: r, .. } = expr {
+                    Ok(Expression::RationalNumberLiteral {
+                        loc: *loc,
+                        ty,
+                        value: -r,
+                    })
+                } else {
+                    get_int_length(&expr_type, loc, false, ns, diagnostics)?;
+
+                    Ok(Expression::UnaryMinus {
+                        loc: *loc,
+                        ty: expr_type,
+                        expr: Box::new(expr),
+                    })
+                }
+            }
+        },
+        pt::Expression::UnaryPlus(loc, e) => {
+            let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+            used_variable(ns, &expr, symtable);
+            let expr_type = expr.ty();
+
+            get_int_length(&expr_type, loc, false, ns, diagnostics)?;
+
+            Ok(expr)
+        }
+
+        pt::Expression::ConditionalOperator(loc, c, l, r) => {
+            let left = expression(l, context, ns, symtable, diagnostics, resolve_to)?;
+            let right = expression(r, context, ns, symtable, diagnostics, resolve_to)?;
+            check_var_usage_expression(ns, &left, &right, symtable);
+            let cond = expression(c, context, ns, symtable, diagnostics, resolve_to)?;
+            used_variable(ns, &cond, symtable);
+
+            let cond = cond.cast(&c.loc(), &Type::Bool, true, ns, diagnostics)?;
+
+            let ty = coerce(&left.ty(), &l.loc(), &right.ty(), &r.loc(), ns, diagnostics)?;
+            let left = left.cast(&l.loc(), &ty, true, ns, diagnostics)?;
+            let right = right.cast(&r.loc(), &ty, true, ns, diagnostics)?;
+
+            Ok(Expression::ConditionalOperator {
+                loc: *loc,
+                ty,
+                cond: Box::new(cond),
+                true_option: Box::new(left),
+                false_option: Box::new(right),
+            })
+        }
+
+        // pre/post decrement/increment
+        pt::Expression::PostIncrement(loc, var)
+        | pt::Expression::PreIncrement(loc, var)
+        | pt::Expression::PostDecrement(loc, var)
+        | pt::Expression::PreDecrement(loc, var) => {
+            if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "operator not allowed in constant context".to_string(),
+                ));
+                return Err(());
+            };
+
+            incr_decr(var, expr, context, ns, symtable, diagnostics)
+        }
+
+        // assignment
+        pt::Expression::Assign(loc, var, e) => {
+            if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "assignment not allowed in constant context".to_string(),
+                ));
+                return Err(());
+            };
+
+            assign_single(loc, var, e, context, ns, symtable, diagnostics)
+        }
+
+        pt::Expression::AssignAdd(loc, var, e)
+        | pt::Expression::AssignSubtract(loc, var, e)
+        | pt::Expression::AssignMultiply(loc, var, e)
+        | pt::Expression::AssignDivide(loc, var, e)
+        | pt::Expression::AssignModulo(loc, var, e)
+        | pt::Expression::AssignOr(loc, var, e)
+        | pt::Expression::AssignAnd(loc, var, e)
+        | pt::Expression::AssignXor(loc, var, e)
+        | pt::Expression::AssignShiftLeft(loc, var, e)
+        | pt::Expression::AssignShiftRight(loc, var, e) => {
+            if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "assignment not allowed in constant context".to_string(),
+                ));
+                return Err(());
+            };
+            let expr = assign_expr(loc, var, expr, e, context, ns, symtable, diagnostics);
+            if let Ok(expression) = &expr {
+                expression.recurse(ns, check_term_for_constant_overflow);
+            }
+            expr
+        }
+        pt::Expression::NamedFunctionCall(loc, ty, args) => named_call_expr(
+            loc,
+            ty,
+            args,
+            false,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            resolve_to,
+        ),
+        pt::Expression::New(loc, call) => {
+            if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    expr.loc(),
+                    "new not allowed in constant expression".to_string(),
+                ));
+                return Err(());
+            }
+
+            match call.remove_parenthesis() {
+                pt::Expression::FunctionCall(_, ty, args) => {
+                    let res = new(loc, ty, args, context, ns, symtable, diagnostics);
+
+                    if let Ok(exp) = &res {
+                        check_function_call(ns, exp, symtable);
+                    }
+                    res
+                }
+                pt::Expression::NamedFunctionCall(_, ty, args) => {
+                    let res =
+                        constructor_named_args(loc, ty, args, context, ns, symtable, diagnostics);
+
+                    if let Ok(exp) = &res {
+                        check_function_call(ns, exp, symtable);
+                    }
+
+                    res
+                }
+                pt::Expression::Variable(id) => {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!("missing constructor arguments to {}", id.name),
+                    ));
+                    Err(())
+                }
+                expr => {
+                    diagnostics.push(Diagnostic::error(
+                        expr.loc(),
+                        "type with arguments expected".into(),
+                    ));
+                    Err(())
+                }
+            }
+        }
+        pt::Expression::Delete(loc, _) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "delete not allowed in expression".to_string(),
+            ));
+            Err(())
+        }
+        pt::Expression::FunctionCall(loc, ty, args) => call_expr(
+            loc,
+            ty,
+            args,
+            false,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            resolve_to,
+        ),
+        pt::Expression::ArraySubscript(loc, _, None) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "expected expression before ']' token".to_string(),
+            ));
+
+            Err(())
+        }
+        pt::Expression::ArraySlice(loc, ..) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "slice not supported yet".to_string(),
+            ));
+
+            Err(())
+        }
+        pt::Expression::ArraySubscript(loc, array, Some(index)) => {
+            array_subscript(loc, array, index, context, ns, symtable, diagnostics)
+        }
+        pt::Expression::MemberAccess(loc, e, id) => member_access(
+            loc,
+            e.remove_parenthesis(),
+            id,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            resolve_to,
+        ),
+        pt::Expression::Or(loc, left, right) => {
+            let boolty = Type::Bool;
+            let l = expression(
+                left,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&boolty),
+            )?
+            .cast(loc, &boolty, true, ns, diagnostics)?;
+            let r = expression(
+                right,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&boolty),
+            )?
+            .cast(loc, &boolty, true, ns, diagnostics)?;
+
+            check_var_usage_expression(ns, &l, &r, symtable);
+
+            Ok(Expression::Or {
+                loc: *loc,
+                left: Box::new(l),
+                right: Box::new(r),
+            })
+        }
+        pt::Expression::And(loc, left, right) => {
+            let boolty = Type::Bool;
+            let l = expression(
+                left,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&boolty),
+            )?
+            .cast(loc, &boolty, true, ns, diagnostics)?;
+            let r = expression(
+                right,
+                context,
+                ns,
+                symtable,
+                diagnostics,
+                ResolveTo::Type(&boolty),
+            )?
+            .cast(loc, &boolty, true, ns, diagnostics)?;
+            check_var_usage_expression(ns, &l, &r, symtable);
+
+            Ok(Expression::And {
+                loc: *loc,
+                left: Box::new(l),
+                right: Box::new(r),
+            })
+        }
+        pt::Expression::Type(loc, _) => {
+            diagnostics.push(Diagnostic::error(*loc, "type not expected".to_owned()));
+            Err(())
+        }
+        pt::Expression::List(loc, _) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "lists only permitted in destructure statements".to_owned(),
+            ));
+            Err(())
+        }
+        pt::Expression::FunctionCallBlock(loc, ..) => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                "unexpect block encountered".to_owned(),
+            ));
+            Err(())
+        }
+        pt::Expression::Unit(loc, expr, unit) => {
+            match unit {
+                pt::Unit::Wei(loc) | pt::Unit::Gwei(loc) | pt::Unit::Ether(loc)
+                    if ns.target != crate::Target::EVM =>
+                {
+                    diagnostics.push(Diagnostic::warning(
+                        *loc,
+                        "ethereum currency unit used while not targetting ethereum".to_owned(),
+                    ));
+                }
+                _ => (),
+            }
+
+            let unit = match unit {
+                pt::Unit::Seconds(_) => BigInt::from(1),
+                pt::Unit::Minutes(_) => BigInt::from(60),
+                pt::Unit::Hours(_) => BigInt::from(60 * 60),
+                pt::Unit::Days(_) => BigInt::from(60 * 60 * 24),
+                pt::Unit::Weeks(_) => BigInt::from(60 * 60 * 24 * 7),
+                pt::Unit::Wei(_) => BigInt::from(1),
+                pt::Unit::Gwei(_) => BigInt::from(10).pow(9u32),
+                pt::Unit::Ether(_) => BigInt::from(10).pow(18u32),
+            };
+
+            match expr.as_ref() {
+                pt::Expression::NumberLiteral(_, integer, exp) => {
+                    number_literal(loc, integer, exp, ns, &unit, diagnostics, resolve_to)
+                }
+                pt::Expression::RationalNumberLiteral(_, significant, mantissa, exp) => {
+                    rational_number_literal(
+                        loc,
+                        significant,
+                        mantissa,
+                        exp,
+                        &unit,
+                        ns,
+                        diagnostics,
+                        resolve_to,
+                    )
+                }
+                pt::Expression::HexNumberLiteral(loc, _) => {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        "hexadecimal numbers cannot be used with unit denominations".to_owned(),
+                    ));
+                    Err(())
+                }
+                _ => {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        "unit denominations can only be used with number literals".to_owned(),
+                    ));
+                    Err(())
+                }
+            }
+        }
+        pt::Expression::This(loc) => match context.contract_no {
+            Some(contract_no) => Ok(Expression::Builtin {
+                loc: *loc,
+                tys: vec![Type::Contract(contract_no)],
+                kind: Builtin::GetAddress,
+                args: Vec::new(),
+            }),
+            None => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "this not allowed outside contract".to_owned(),
+                ));
+                Err(())
+            }
+        },
+    }
+}

+ 84 - 0
src/sema/expression/retrieve_type.rs

@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, RetrieveType, Type};
+
+impl RetrieveType for Expression {
+    fn ty(&self) -> Type {
+        match self {
+            Expression::BoolLiteral { .. }
+            | Expression::More { .. }
+            | Expression::Less { .. }
+            | Expression::MoreEqual { .. }
+            | Expression::LessEqual { .. }
+            | Expression::Equal { .. }
+            | Expression::Or { .. }
+            | Expression::And { .. }
+            | Expression::NotEqual { .. }
+            | Expression::Not { .. }
+            | Expression::StringCompare { .. } => Type::Bool,
+            Expression::CodeLiteral { .. } => Type::DynamicBytes,
+            Expression::StringConcat { ty, .. }
+            | Expression::BytesLiteral { ty, .. }
+            | Expression::NumberLiteral { ty, .. }
+            | Expression::RationalNumberLiteral { ty, .. }
+            | Expression::StructLiteral { ty, .. }
+            | Expression::ArrayLiteral { ty, .. }
+            | Expression::ConstArrayLiteral { ty, .. }
+            | Expression::Add { ty, .. }
+            | Expression::Subtract { ty, .. }
+            | Expression::Multiply { ty, .. }
+            | Expression::Divide { ty, .. }
+            | Expression::Modulo { ty, .. }
+            | Expression::Power { ty, .. }
+            | Expression::BitwiseOr { ty, .. }
+            | Expression::BitwiseAnd { ty, .. }
+            | Expression::BitwiseXor { ty, .. }
+            | Expression::ShiftLeft { ty, .. }
+            | Expression::ShiftRight { ty, .. }
+            | Expression::Variable { ty, .. }
+            | Expression::ConstantVariable { ty, .. }
+            | Expression::StorageVariable { ty, .. }
+            | Expression::Load { ty, .. }
+            | Expression::GetRef { ty, .. }
+            | Expression::StorageLoad { ty, .. }
+            | Expression::Complement { ty, .. }
+            | Expression::UnaryMinus { ty, .. }
+            | Expression::ConditionalOperator { ty, .. }
+            | Expression::StructMember { ty, .. }
+            | Expression::AllocDynamicBytes { ty, .. }
+            | Expression::PreIncrement { ty, .. }
+            | Expression::PreDecrement { ty, .. }
+            | Expression::PostIncrement { ty, .. }
+            | Expression::PostDecrement { ty, .. }
+            | Expression::Assign { ty, .. } => ty.clone(),
+            Expression::Subscript { ty, .. } => ty.clone(),
+            Expression::ZeroExt { to, .. }
+            | Expression::SignExt { to, .. }
+            | Expression::Trunc { to, .. }
+            | Expression::CheckingTrunc { to, .. }
+            | Expression::Cast { to, .. }
+            | Expression::BytesCast { to, .. } => to.clone(),
+            Expression::StorageArrayLength { ty, .. } => ty.clone(),
+            Expression::ExternalFunctionCallRaw { .. } => {
+                panic!("two return values");
+            }
+            Expression::Builtin { tys: returns, .. }
+            | Expression::InternalFunctionCall { returns, .. }
+            | Expression::ExternalFunctionCall { returns, .. } => {
+                assert_eq!(returns.len(), 1);
+                returns[0].clone()
+            }
+            Expression::List { list, .. } => {
+                assert_eq!(list.len(), 1);
+
+                list[0].ty()
+            }
+            Expression::Constructor { contract_no, .. } => Type::Contract(*contract_no),
+            Expression::InterfaceId { .. } => Type::FunctionSelector,
+            Expression::FormatString { .. } => Type::String,
+            // codegen Expressions
+            Expression::InternalFunction { ty, .. } => ty.clone(),
+            Expression::ExternalFunction { ty, .. } => ty.clone(),
+        }
+    }
+}

+ 108 - 0
src/sema/expression/strings.rs

@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::diagnostics::Diagnostics;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+
+/// Unescape a string literal
+pub(crate) fn unescape(
+    literal: &str,
+    start: usize,
+    file_no: usize,
+    diagnostics: &mut Diagnostics,
+) -> Vec<u8> {
+    let mut s: Vec<u8> = Vec::new();
+    let mut indeces = literal.char_indices();
+
+    while let Some((_, ch)) = indeces.next() {
+        if ch != '\\' {
+            let mut buffer = [0; 4];
+            s.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
+            continue;
+        }
+
+        match indeces.next() {
+            Some((_, '\n')) => (),
+            Some((_, '\\')) => s.push(b'\\'),
+            Some((_, '\'')) => s.push(b'\''),
+            Some((_, '"')) => s.push(b'"'),
+            Some((_, 'b')) => s.push(b'\x08'),
+            Some((_, 'f')) => s.push(b'\x0c'),
+            Some((_, 'n')) => s.push(b'\n'),
+            Some((_, 'r')) => s.push(b'\r'),
+            Some((_, 't')) => s.push(b'\t'),
+            Some((_, 'v')) => s.push(b'\x0b'),
+            Some((i, 'x')) => match get_digits(&mut indeces, 2) {
+                Ok(ch) => s.push(ch as u8),
+                Err(offset) => {
+                    diagnostics.push(Diagnostic::error(
+                        pt::Loc::File(
+                            file_no,
+                            start + i,
+                            start + std::cmp::min(literal.len(), offset),
+                        ),
+                        "\\x escape should be followed by two hex digits".to_string(),
+                    ));
+                }
+            },
+            Some((i, 'u')) => match get_digits(&mut indeces, 4) {
+                Ok(codepoint) => match char::from_u32(codepoint) {
+                    Some(ch) => {
+                        let mut buffer = [0; 4];
+                        s.extend_from_slice(ch.encode_utf8(&mut buffer).as_bytes());
+                    }
+                    None => {
+                        diagnostics.push(Diagnostic::error(
+                            pt::Loc::File(file_no, start + i, start + i + 6),
+                            "Found an invalid unicode character".to_string(),
+                        ));
+                    }
+                },
+                Err(offset) => {
+                    diagnostics.push(Diagnostic::error(
+                        pt::Loc::File(
+                            file_no,
+                            start + i,
+                            start + std::cmp::min(literal.len(), offset),
+                        ),
+                        "\\u escape should be followed by four hex digits".to_string(),
+                    ));
+                }
+            },
+            Some((i, ch)) => {
+                diagnostics.push(Diagnostic::error(
+                    pt::Loc::File(file_no, start + i, start + i + ch.len_utf8()),
+                    format!("unknown escape character '{}'", ch),
+                ));
+            }
+            None => unreachable!(),
+        }
+    }
+    s
+}
+
+/// Get the hex digits for an escaped \x or \u. Returns either the value or
+/// or the offset of the last character
+pub(super) fn get_digits(input: &mut std::str::CharIndices, len: usize) -> Result<u32, usize> {
+    let mut n: u32 = 0;
+    let offset;
+
+    for _ in 0..len {
+        if let Some((_, ch)) = input.next() {
+            if let Some(v) = ch.to_digit(16) {
+                n = (n << 4) + v;
+                continue;
+            }
+            offset = match input.next() {
+                Some((i, _)) => i,
+                None => std::usize::MAX,
+            };
+        } else {
+            offset = std::usize::MAX;
+        }
+
+        return Err(offset);
+    }
+
+    Ok(n)
+}

+ 178 - 0
src/sema/expression/subscript.rs

@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, Namespace, RetrieveType, Type};
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::eval::check_term_for_constant_overflow;
+use crate::sema::expression::resolve_expression::expression;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use crate::sema::Recurse;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+use solang_parser::pt::CodeLocation;
+
+/// Resolve an array subscript expression
+pub(super) fn array_subscript(
+    loc: &pt::Loc,
+    array: &pt::Expression,
+    index: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let mut array = expression(
+        array,
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Unknown,
+    )?;
+    let array_ty = array.ty();
+
+    if array.ty().is_mapping() {
+        return mapping_subscript(loc, array, index, context, ns, symtable, diagnostics);
+    }
+
+    let index_width_ty = if array_ty.is_contract_storage() && !array_ty.is_storage_bytes() {
+        Type::Uint(256)
+    } else {
+        Type::Uint(32)
+    };
+
+    let mut index = expression(
+        index,
+        context,
+        ns,
+        symtable,
+        diagnostics,
+        ResolveTo::Type(&index_width_ty),
+    )?;
+
+    let index_ty = index.ty();
+
+    index.recurse(ns, check_term_for_constant_overflow);
+
+    match index_ty.deref_any() {
+        Type::Uint(_) => (),
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                *loc,
+                format!(
+                    "array subscript must be an unsigned integer, not '{}'",
+                    index.ty().to_string(ns)
+                ),
+            ));
+            return Err(());
+        }
+    };
+
+    if array_ty.is_storage_bytes() {
+        return Ok(Expression::Subscript {
+            loc: *loc,
+            ty: Type::StorageRef(false, Box::new(Type::Bytes(1))),
+            array_ty,
+            array: Box::new(array),
+            index: Box::new(index.cast(&index.loc(), &Type::Uint(32), false, ns, diagnostics)?),
+        });
+    }
+
+    // make sure we load the index value if needed
+    index = index.cast(&index.loc(), index_ty.deref_any(), true, ns, diagnostics)?;
+
+    let deref_ty = array_ty.deref_any();
+    match deref_ty {
+        Type::Bytes(_) | Type::Array(..) | Type::DynamicBytes => {
+            if array_ty.is_contract_storage() {
+                let elem_ty = array_ty.storage_array_elem();
+
+                // When subscripting a bytes32 type array, we need to load it. It is not
+                // assignable and the value is calculated by shifting in codegen
+                if let Type::Bytes(_) = deref_ty {
+                    array = array.cast(&array.loc(), deref_ty, true, ns, diagnostics)?;
+                }
+
+                Ok(Expression::Subscript {
+                    loc: *loc,
+                    ty: elem_ty,
+                    array_ty,
+                    array: Box::new(array),
+                    index: Box::new(index),
+                })
+            } else {
+                let elem_ty = array_ty.array_deref();
+
+                array = array.cast(
+                    &array.loc(),
+                    if array_ty.deref_memory().is_fixed_reference_type() {
+                        &array_ty
+                    } else {
+                        array_ty.deref_any()
+                    },
+                    true,
+                    ns,
+                    diagnostics,
+                )?;
+
+                Ok(Expression::Subscript {
+                    loc: *loc,
+                    ty: elem_ty,
+                    array_ty,
+                    array: Box::new(array),
+                    index: Box::new(index),
+                })
+            }
+        }
+        Type::String => {
+            diagnostics.push(Diagnostic::error(
+                array.loc(),
+                "array subscript is not permitted on string".to_string(),
+            ));
+            Err(())
+        }
+        _ => {
+            diagnostics.push(Diagnostic::error(
+                array.loc(),
+                "expression is not an array".to_string(),
+            ));
+            Err(())
+        }
+    }
+}
+
+/// Calculate storage subscript
+fn mapping_subscript(
+    loc: &pt::Loc,
+    mapping: Expression,
+    index: &pt::Expression,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+) -> Result<Expression, ()> {
+    let ty = mapping.ty();
+    let elem_ty = ty.storage_array_elem();
+
+    if let Type::Mapping(key_ty, _) = ty.deref_any() {
+        let index_expr = expression(
+            index,
+            context,
+            ns,
+            symtable,
+            diagnostics,
+            ResolveTo::Type(key_ty),
+        )?
+        .cast(&index.loc(), key_ty, true, ns, diagnostics)?;
+
+        Ok(Expression::Subscript {
+            loc: *loc,
+            ty: elem_ty,
+            array_ty: ty,
+            array: Box::new(mapping),
+            index: Box::new(index_expr),
+        })
+    } else {
+        unreachable!()
+    }
+}

+ 18 - 0
src/sema/expression/tests.rs

@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#![cfg(test)]
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::strings::unescape;
+
+#[test]
+fn test_unescape() {
+    let s = r#"\u00f3"#;
+    let mut vec = Diagnostics::default();
+    let res = unescape(s, 0, 0, &mut vec);
+    assert!(vec.is_empty());
+    assert_eq!(res, vec![0xc3, 0xb3]);
+    let s = r#"\xff"#;
+    let res = unescape(s, 0, 0, &mut vec);
+    assert!(vec.is_empty());
+    assert_eq!(res, vec![255]);
+}

+ 159 - 0
src/sema/expression/variable.rs

@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::sema::ast::{Expression, Namespace, Symbol, Type};
+use crate::sema::builtin;
+use crate::sema::diagnostics::Diagnostics;
+use crate::sema::expression::function_call::available_functions;
+use crate::sema::expression::{ExprContext, ResolveTo};
+use crate::sema::symtable::Symtable;
+use solang_parser::diagnostics::Diagnostic;
+use solang_parser::pt;
+
+pub(super) fn variable(
+    id: &pt::Identifier,
+    context: &ExprContext,
+    ns: &mut Namespace,
+    symtable: &mut Symtable,
+    diagnostics: &mut Diagnostics,
+    resolve_to: ResolveTo,
+) -> Result<Expression, ()> {
+    if let Some(v) = symtable.find(&id.name) {
+        return if context.constant {
+            diagnostics.push(Diagnostic::error(
+                id.loc,
+                format!("cannot read variable '{}' in constant expression", id.name),
+            ));
+            Err(())
+        } else {
+            Ok(Expression::Variable {
+                loc: id.loc,
+                ty: v.ty.clone(),
+                var_no: v.pos,
+            })
+        };
+    }
+
+    if let Some((builtin, ty)) = builtin::builtin_var(&id.loc, None, &id.name, ns, diagnostics) {
+        return Ok(Expression::Builtin {
+            loc: id.loc,
+            tys: vec![ty],
+            kind: builtin,
+            args: vec![],
+        });
+    }
+
+    // are we trying to resolve a function type?
+    let function_first = if let ResolveTo::Type(resolve_to) = resolve_to {
+        matches!(
+            resolve_to,
+            Type::InternalFunction { .. } | Type::ExternalFunction { .. }
+        )
+    } else {
+        false
+    };
+
+    match ns.resolve_var(context.file_no, context.contract_no, id, function_first) {
+        Some(Symbol::Variable(_, Some(var_contract_no), var_no)) => {
+            let var_contract_no = *var_contract_no;
+            let var_no = *var_no;
+
+            let var = &ns.contracts[var_contract_no].variables[var_no];
+
+            if var.constant {
+                Ok(Expression::ConstantVariable {
+                    loc: id.loc,
+                    ty: var.ty.clone(),
+                    contract_no: Some(var_contract_no),
+                    var_no,
+                })
+            } else if context.constant {
+                diagnostics.push(Diagnostic::error(
+                    id.loc,
+                    format!(
+                        "cannot read contract variable '{}' in constant expression",
+                        id.name
+                    ),
+                ));
+                Err(())
+            } else {
+                Ok(Expression::StorageVariable {
+                    loc: id.loc,
+                    ty: Type::StorageRef(var.immutable, Box::new(var.ty.clone())),
+                    contract_no: var_contract_no,
+                    var_no,
+                })
+            }
+        }
+        Some(Symbol::Variable(_, None, var_no)) => {
+            let var_no = *var_no;
+
+            let var = &ns.constants[var_no];
+
+            Ok(Expression::ConstantVariable {
+                loc: id.loc,
+                ty: var.ty.clone(),
+                contract_no: None,
+                var_no,
+            })
+        }
+        Some(Symbol::Function(_)) => {
+            let mut name_matches = 0;
+            let mut expr = None;
+
+            for function_no in
+                available_functions(&id.name, true, context.file_no, context.contract_no, ns)
+            {
+                let func = &ns.functions[function_no];
+
+                if func.ty != pt::FunctionTy::Function {
+                    continue;
+                }
+
+                let ty = Type::InternalFunction {
+                    params: func.params.iter().map(|p| p.ty.clone()).collect(),
+                    mutability: func.mutability.clone(),
+                    returns: func.returns.iter().map(|p| p.ty.clone()).collect(),
+                };
+
+                name_matches += 1;
+                expr = Some(Expression::InternalFunction {
+                    loc: id.loc,
+                    ty,
+                    function_no,
+                    signature: if func.is_virtual || func.is_override.is_some() {
+                        Some(func.signature.clone())
+                    } else {
+                        None
+                    },
+                });
+            }
+
+            if name_matches == 1 {
+                Ok(expr.unwrap())
+            } else {
+                diagnostics.push(Diagnostic::error(
+                    id.loc,
+                    format!("function '{}' is overloaded", id.name),
+                ));
+                Err(())
+            }
+        }
+        None if id.name == "now"
+            && matches!(
+                resolve_to,
+                ResolveTo::Type(Type::Uint(_)) | ResolveTo::Integer
+            ) =>
+        {
+            diagnostics.push(
+                    Diagnostic::error(
+                        id.loc,
+                        "'now' not found. 'now' was an alias for 'block.timestamp' in older versions of the Solidity language. Please use 'block.timestamp' instead.".to_string(),
+                    ));
+            Err(())
+        }
+        sym => {
+            diagnostics.push(Namespace::wrong_symbol(sym, id));
+            Err(())
+        }
+    }
+}

+ 2 - 1
src/sema/format.rs

@@ -1,12 +1,13 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use super::ast::{Diagnostic, Expression, FormatArg, Namespace, RetrieveType, Type};
-use super::expression::{expression, ExprContext, ResolveTo};
+use super::expression::{ExprContext, ResolveTo};
 use super::symtable::Symtable;
 use crate::sema::diagnostics::Diagnostics;
 use solang_parser::pt;
 use solang_parser::pt::CodeLocation;
 
+use crate::sema::expression::resolve_expression::expression;
 use std::iter::Peekable;
 use std::slice::Iter;
 use std::str::CharIndices;

+ 4 - 1
src/sema/function_annotation.rs

@@ -4,10 +4,13 @@ use super::{
     ast::{ConstructorAnnotation, Diagnostic, Expression, Function, Namespace, Type},
     diagnostics::Diagnostics,
     eval::overflow_check,
-    expression::{expression, hex_number_literal, number_literal, ExprContext, ResolveTo},
+    expression::literals::hex_number_literal,
+    expression::{ExprContext, ResolveTo},
     unused_variable::used_variable,
     Symtable,
 };
+use crate::sema::expression::literals::number_literal;
+use crate::sema::expression::resolve_expression::expression;
 use crate::Target;
 use num_bigint::BigInt;
 use num_traits::{One, ToPrimitive};

+ 2 - 1
src/sema/namespace.rs

@@ -7,11 +7,12 @@ use super::{
     builtin,
     diagnostics::Diagnostics,
     eval::eval_const_number,
-    expression::{expression, ExprContext, ResolveTo},
+    expression::{ExprContext, ResolveTo},
     resolve_params, resolve_returns,
     symtable::Symtable,
     ArrayDimension,
 };
+use crate::sema::expression::resolve_expression::expression;
 use crate::Target;
 use num_bigint::BigInt;
 use num_traits::Signed;

+ 9 - 3
src/sema/statements.rs

@@ -5,12 +5,18 @@ use super::contracts::is_base;
 use super::diagnostics::Diagnostics;
 use super::eval::check_term_for_constant_overflow;
 use super::expression::{
-    available_functions, call_expr, constructor_named_args, expression, function_call_expr,
-    function_call_pos_args, match_constructor_to_args, named_call_expr, named_function_call_expr,
-    new, ExprContext, ResolveTo,
+    function_call::{available_functions, call_expr, named_call_expr},
+    ExprContext, ResolveTo,
 };
 use super::symtable::{LoopScopes, Symtable};
 use crate::sema::builtin;
+use crate::sema::expression::constructor::{
+    constructor_named_args, match_constructor_to_args, new,
+};
+use crate::sema::expression::function_call::{
+    function_call_expr, function_call_pos_args, named_function_call_expr,
+};
+use crate::sema::expression::resolve_expression::expression;
 use crate::sema::function_annotation::function_body_annotations;
 use crate::sema::symtable::{VariableInitializer, VariableUsage};
 use crate::sema::unused_variable::{assigned_variable, check_function_call, used_variable};

+ 0 - 15
src/sema/tests/mod.rs

@@ -2,8 +2,6 @@
 
 #![cfg(test)]
 use crate::sema::ast::{Expression, Parameter, Statement, TryCatch, Type};
-use crate::sema::diagnostics::Diagnostics;
-use crate::sema::expression::unescape;
 use crate::sema::yul::ast::InlineAssembly;
 use crate::{parse_and_resolve, sema::ast, FileResolver, Target};
 use solang_parser::pt::Loc;
@@ -17,19 +15,6 @@ pub(crate) fn parse(src: &'static str) -> ast::Namespace {
     ns
 }
 
-#[test]
-fn test_unescape() {
-    let s = r#"\u00f3"#;
-    let mut vec = Diagnostics::default();
-    let res = unescape(s, 0, 0, &mut vec);
-    assert!(vec.is_empty());
-    assert_eq!(res, vec![0xc3, 0xb3]);
-    let s = r#"\xff"#;
-    let res = unescape(s, 0, 0, &mut vec);
-    assert!(vec.is_empty());
-    assert_eq!(res, vec![255]);
-}
-
 #[test]
 fn test_statement_reachable() {
     let loc = Loc::File(0, 1, 2);

+ 3 - 1
src/sema/using.rs

@@ -3,9 +3,11 @@
 use super::{
     ast::{Diagnostic, Expression, Namespace, Note, Type, Using, UsingList},
     diagnostics::Diagnostics,
-    expression::{expression, function_returns, function_type, ExprContext, ResolveTo},
+    expression::{ExprContext, ResolveTo},
     symtable::Symtable,
 };
+use crate::sema::expression::function_call::{function_returns, function_type};
+use crate::sema::expression::resolve_expression::expression;
 use solang_parser::pt;
 use solang_parser::pt::CodeLocation;
 use std::collections::HashSet;

+ 2 - 1
src/sema/variables.rs

@@ -8,13 +8,14 @@ use super::{
     },
     contracts::is_base,
     diagnostics::Diagnostics,
-    expression::{expression, ExprContext, ResolveTo},
+    expression::{ExprContext, ResolveTo},
     symtable::Symtable,
     symtable::{VariableInitializer, VariableUsage},
     tags::resolve_tags,
     ContractDefinition,
 };
 use crate::sema::eval::check_term_for_constant_overflow;
+use crate::sema::expression::resolve_expression::expression;
 use crate::sema::Recurse;
 use solang_parser::{
     doccomment::DocComment,

+ 1 - 1
src/sema/yul/expression.rs

@@ -2,7 +2,7 @@
 
 use crate::sema::ast::{ArrayLength, Namespace, Parameter, Symbol, Type};
 use crate::sema::diagnostics::Diagnostics;
-use crate::sema::expression::{unescape, ExprContext};
+use crate::sema::expression::{strings::unescape, ExprContext};
 use crate::sema::symtable::{Symtable, VariableUsage};
 use crate::sema::yul::ast::{YulExpression, YulSuffix};
 use crate::sema::yul::builtin::{parse_builtin_keyword, yul_unsupported_builtin};