Bladeren bron

Implement unused variable detection

Signed-off-by: LucasSte <lucas.tnagel@gmail.com>
LucasSte 4 jaren geleden
bovenliggende
commit
edfaf769ff

+ 2 - 0
.gitignore

@@ -2,3 +2,5 @@
 Cargo.lock
 /target
 **/*.rs.bk
+.DS_Store
+.idea

+ 3 - 0
src/sema/ast.rs

@@ -62,6 +62,7 @@ pub struct EventDecl {
     pub fields: Vec<Parameter>,
     pub signature: String,
     pub anonymous: bool,
+    pub used: bool,
 }
 
 impl fmt::Display for EventDecl {
@@ -284,6 +285,8 @@ pub struct Variable {
     pub visibility: pt::Visibility,
     pub constant: bool,
     pub initializer: Option<Expression>,
+    pub assigned: bool,
+    pub read: bool,
 }
 
 #[derive(Clone, PartialEq)]

+ 2 - 2
src/sema/builtin.rs

@@ -467,7 +467,7 @@ pub fn resolve_call(
     args: &[pt::Expression],
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -590,7 +590,7 @@ pub fn resolve_method_call(
     args: &[pt::Expression],
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     // The abi.* functions need special handling, others do not

+ 9 - 2
src/sema/contracts.rs

@@ -13,6 +13,7 @@ use super::statements;
 use super::symtable::Symtable;
 use super::variables;
 use super::{ast, SOLANA_FIRST_OFFSET};
+use crate::sema::unused_variable::emit_warning_local_variable;
 use crate::{emit, Target};
 
 impl ast::Contract {
@@ -249,7 +250,7 @@ fn resolve_base_args(
                     .position(|e| e.contract_no == base_no)
                 {
                     if let Some(args) = &base.args {
-                        let symtable = Symtable::new();
+                        let mut symtable = Symtable::new();
 
                         // find constructor which matches this
                         if let Ok((Some(constructor_no), args)) = match_constructor_to_args(
@@ -259,7 +260,7 @@ fn resolve_base_args(
                             base_no,
                             *contract_no,
                             ns,
-                            &symtable,
+                            &mut symtable,
                             &mut diagnostics,
                         ) {
                             ns.contracts[*contract_no].bases[pos].constructor =
@@ -868,6 +869,12 @@ fn resolve_bodies(
             .is_err()
         {
             broken = true;
+        } else {
+            for (_, variable) in &ns.functions[function_no].symtable.vars {
+                if let Some(warning) = emit_warning_local_variable(&variable) {
+                    ns.diagnostics.push(*warning);
+                }
+            }
         }
     }
 

+ 130 - 60
src/sema/expression.rs

@@ -22,6 +22,9 @@ use super::eval::eval_const_number;
 use super::format::string_format;
 use super::symtable::Symtable;
 use crate::parser::pt;
+use crate::sema::unused_variable::{
+    assigned_variable, check_function_call, check_var_usage_expression, used_variable,
+};
 use crate::Target;
 use base58::{FromBase58, FromBase58Error};
 
@@ -1312,7 +1315,7 @@ pub fn expression(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
     resolve_to: Option<&Type>,
@@ -1704,6 +1707,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -1744,6 +1749,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -1784,6 +1791,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -1824,6 +1833,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -1864,6 +1875,7 @@ pub fn expression(
                 None,
             )?;
 
+            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)?;
@@ -1900,6 +1912,8 @@ pub fn expression(
                 None,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let left_type = left.ty();
             // left hand side may be bytes/int/uint
             // right hand size may be int/uint
@@ -1936,6 +1950,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -2005,6 +2021,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -2045,6 +2063,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
+
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -2114,6 +2134,8 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            check_var_usage_expression(ns, &base, &exp, symtable);
+
             let base_type = base.ty();
             let exp_type = exp.ty();
 
@@ -2168,6 +2190,7 @@ pub fn expression(
                 None,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -2207,6 +2230,7 @@ pub fn expression(
                 None,
             )?;
 
+            check_var_usage_expression(ns, &left, &right, symtable);
             let ty = coerce_int(
                 &left.ty(),
                 &l.loc(),
@@ -2245,6 +2269,7 @@ pub fn expression(
                 diagnostics,
                 None,
             )?;
+            check_var_usage_expression(ns, &left, &right, symtable);
 
             let ty = coerce_int(
                 &left.ty(),
@@ -2284,6 +2309,7 @@ pub fn expression(
                 diagnostics,
                 None,
             )?;
+            check_var_usage_expression(ns, &left, &right, symtable);
 
             let ty = coerce_int(
                 &left.ty(),
@@ -2341,6 +2367,7 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            used_variable(ns, &expr, symtable);
             Ok(Expression::Not(
                 *loc,
                 Box::new(cast(&loc, expr, &Type::Bool, true, ns, diagnostics)?),
@@ -2358,6 +2385,7 @@ pub fn expression(
                 resolve_to,
             )?;
 
+            used_variable(ns, &expr, symtable);
             let expr_ty = expr.ty();
 
             get_int_length(&expr_ty, loc, true, ns, diagnostics)?;
@@ -2388,6 +2416,7 @@ pub fn expression(
                     resolve_to,
                 )?;
 
+                used_variable(ns, &expr, symtable);
                 let expr_type = expr.ty();
 
                 if let Expression::NumberLiteral(_, _, n) = expr {
@@ -2410,6 +2439,7 @@ pub fn expression(
                 diagnostics,
                 resolve_to,
             )?;
+            used_variable(ns, &expr, symtable);
             let expr_type = expr.ty();
 
             get_int_length(&expr_type, loc, false, ns, diagnostics)?;
@@ -2438,6 +2468,7 @@ pub fn expression(
                 diagnostics,
                 resolve_to,
             )?;
+            check_var_usage_expression(ns, &left, &right, symtable);
             let cond = expression(
                 c,
                 file_no,
@@ -2569,6 +2600,7 @@ pub fn expression(
                 diagnostics,
             )?;
 
+            check_function_call(ns, &expr, symtable);
             if expr.tys().len() > 1 {
                 diagnostics.push(Diagnostic::error(
                     *loc,
@@ -2590,26 +2622,41 @@ pub fn expression(
             }
 
             match call.as_ref() {
-                pt::Expression::FunctionCall(_, ty, args) => new(
-                    loc,
-                    ty,
-                    args,
-                    file_no,
-                    contract_no,
-                    ns,
-                    symtable,
-                    diagnostics,
-                ),
-                pt::Expression::NamedFunctionCall(_, ty, args) => constructor_named_args(
-                    loc,
-                    ty,
-                    args,
-                    file_no,
-                    contract_no,
-                    ns,
-                    symtable,
-                    diagnostics,
-                ),
+                pt::Expression::FunctionCall(_, ty, args) => {
+                    let res = new(
+                        loc,
+                        ty,
+                        args,
+                        file_no,
+                        contract_no,
+                        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,
+                        file_no,
+                        contract_no,
+                        ns,
+                        symtable,
+                        diagnostics,
+                    );
+
+                    if let Ok(exp) = &res {
+                        check_function_call(ns, &exp, symtable);
+                    }
+
+                    res
+                }
                 _ => unreachable!(),
             }
         }
@@ -2681,6 +2728,7 @@ pub fn expression(
                 diagnostics,
             )?;
 
+            check_function_call(ns, &expr, symtable);
             if expr.tys().len() > 1 {
                 diagnostics.push(Diagnostic::error(
                     *loc,
@@ -2711,18 +2759,26 @@ pub fn expression(
             is_constant,
             diagnostics,
         ),
-        pt::Expression::MemberAccess(loc, e, id) => member_access(
-            loc,
-            e,
-            id,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            is_constant,
-            diagnostics,
-            resolve_to,
-        ),
+        pt::Expression::MemberAccess(loc, e, id) => {
+            let res = member_access(
+                loc,
+                e,
+                id,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                is_constant,
+                diagnostics,
+                resolve_to,
+            );
+
+            if let Ok(exp) = &res {
+                used_variable(ns, &exp, symtable);
+            }
+
+            res
+        }
         pt::Expression::Or(loc, left, right) => {
             let boolty = Type::Bool;
             let l = cast(
@@ -2760,6 +2816,8 @@ pub fn expression(
                 diagnostics,
             )?;
 
+            check_var_usage_expression(ns, &l, &r, symtable);
+
             Ok(Expression::Or(*loc, Box::new(l), Box::new(r)))
         }
         pt::Expression::And(loc, left, right) => {
@@ -2798,6 +2856,7 @@ pub fn expression(
                 ns,
                 diagnostics,
             )?;
+            check_var_usage_expression(ns, &l, &r, symtable);
 
             Ok(Expression::And(*loc, Box::new(l), Box::new(r)))
         }
@@ -2898,7 +2957,7 @@ fn constructor(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     // The current contract cannot be constructed with new. In order to create
@@ -2985,7 +3044,7 @@ pub fn match_constructor_to_args(
     contract_no: usize,
     args_contact_no: usize,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<(Option<usize>, Vec<Expression>), ()> {
     let marker = diagnostics.len();
@@ -3093,7 +3152,7 @@ pub fn constructor_named_args(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let (ty, call_args, _) = collect_call_args(ty, diagnostics)?;
@@ -3417,7 +3476,7 @@ pub fn new(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
@@ -3506,6 +3565,8 @@ pub fn new(
         Some(&Type::Uint(32)),
     )?;
 
+    used_variable(ns, &size_expr, symtable);
+
     let size = cast(&size_loc, size_expr, &Type::Uint(32), true, ns, diagnostics)?;
 
     Ok(Expression::AllocDynamicArray(
@@ -3524,7 +3585,7 @@ fn equal(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -3549,6 +3610,8 @@ fn equal(
         None,
     )?;
 
+    check_var_usage_expression(ns, &left, &right, symtable);
+
     // Comparing stringliteral against stringliteral
     if let (Expression::BytesLiteral(_, _, l), Expression::BytesLiteral(_, _, r)) = (&left, &right)
     {
@@ -3640,7 +3703,7 @@ fn addition(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
     resolve_to: Option<&Type>,
@@ -3665,6 +3728,7 @@ fn addition(
         diagnostics,
         resolve_to,
     )?;
+    check_var_usage_expression(ns, &left, &right, symtable);
 
     // Concatenate stringliteral with stringliteral
     if let (Expression::BytesLiteral(_, _, l), Expression::BytesLiteral(_, _, r)) = (&left, &right)
@@ -3777,7 +3841,7 @@ pub fn assign_single(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let var = expression(
@@ -3790,6 +3854,7 @@ pub fn assign_single(
         diagnostics,
         None,
     )?;
+    assigned_variable(ns, &var, symtable);
     let var_ty = var.ty();
     let val = expression(
         right,
@@ -3801,6 +3866,7 @@ pub fn assign_single(
         diagnostics,
         Some(var_ty.deref_any()),
     )?;
+    used_variable(ns, &val, symtable);
     match &var {
         Expression::ConstantVariable(loc, _, Some(contract_no), var_no) => {
             diagnostics.push(Diagnostic::error(
@@ -3865,7 +3931,7 @@ fn assign_expr(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let var = expression(
@@ -3878,6 +3944,7 @@ fn assign_expr(
         diagnostics,
         None,
     )?;
+    assigned_variable(ns, &var, symtable);
     let var_ty = var.ty();
 
     let resolve_to = if matches!(
@@ -3899,6 +3966,7 @@ fn assign_expr(
         diagnostics,
         resolve_to,
     )?;
+    used_variable(ns, &set, symtable);
     let set_type = set.ty();
 
     let op = |assign: Expression,
@@ -4044,7 +4112,7 @@ fn incr_decr(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let op = |e: Expression, ty: Type| -> Expression {
@@ -4071,6 +4139,7 @@ fn incr_decr(
         diagnostics,
         None,
     )?;
+    used_variable(ns, &var, symtable);
     let var_ty = var.ty();
 
     match &var {
@@ -4217,7 +4286,7 @@ fn member_access(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
     resolve_to: Option<&Type>,
@@ -4345,6 +4414,7 @@ fn member_access(
     match expr_ty {
         Type::Bytes(n) => {
             if id.name == "length" {
+                used_variable(ns, &expr, symtable);
                 return Ok(Expression::NumberLiteral(
                     *loc,
                     Type::Uint(8),
@@ -4465,9 +4535,9 @@ fn member_access(
 
                     if !is_this {
                         diagnostics.push(Diagnostic::error(
-                                    expr.loc(),
-                                        "substrate can only retrieve balance of this, like ‘address(this).balance’".to_string(),
-                                ));
+                            expr.loc(),
+                            "substrate can only retrieve balance of this, like ‘address(this).balance’".to_string(),
+                        ));
                         return Err(());
                     }
                 }
@@ -4564,7 +4634,7 @@ fn array_subscript(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -4692,7 +4762,7 @@ fn struct_literal(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -4751,7 +4821,7 @@ fn call_function_type(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let mut function = expression(
@@ -5004,7 +5074,7 @@ pub fn call_position_args(
     virtual_call: bool,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let mut name_matches = 0;
@@ -5141,7 +5211,7 @@ fn function_call_with_named_args(
     virtual_call: bool,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let mut arguments = HashMap::new();
@@ -5300,7 +5370,7 @@ fn named_struct_literal(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -5369,7 +5439,7 @@ fn method_call_pos_args(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     if let pt::Expression::Variable(namespace) = var {
@@ -5670,7 +5740,6 @@ fn method_call_pos_args(
                             return Err(());
                         }
                     };
-
                     return Ok(Expression::Builtin(
                         func.loc,
                         vec![ret_ty],
@@ -6188,7 +6257,7 @@ fn method_call_named_args(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     if let pt::Expression::Variable(namespace) = var {
@@ -6530,7 +6599,7 @@ fn resolve_array_literal(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
     resolve_to: Option<&Type>,
@@ -6584,6 +6653,7 @@ fn resolve_array_literal(
             diagnostics,
             Some(&ty),
         )?;
+        used_variable(ns, &other, symtable);
 
         if other.ty() != ty {
             other = cast(&e.loc(), other, &ty, true, ns, diagnostics)?;
@@ -6727,7 +6797,7 @@ fn parse_call_args(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<CallArgs, ()> {
     let mut args: HashMap<&String, &pt::NamedArgument> = HashMap::new();
@@ -6918,7 +6988,7 @@ pub fn function_call_expr(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
@@ -7051,7 +7121,7 @@ pub fn named_function_call_expr(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let (ty, call_args, call_args_loc) = collect_call_args(ty, diagnostics)?;
@@ -7147,7 +7217,7 @@ fn mapping_subscript(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     is_constant: bool,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {

+ 1 - 1
src/sema/format.rs

@@ -18,7 +18,7 @@ pub fn string_format(
     file_no: usize,
     contract_no: Option<usize>,
     ns: &mut Namespace,
-    symtable: &Symtable,
+    symtable: &mut Symtable,
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     // first resolve the arguments. We can't say anything about the format string if the args are broken

+ 13 - 4
src/sema/mod.rs

@@ -21,6 +21,7 @@ mod statements;
 pub mod symtable;
 pub mod tags;
 mod types;
+mod unused_variable;
 mod variables;
 
 use self::contracts::visit_bases;
@@ -30,6 +31,7 @@ use self::functions::{resolve_params, resolve_returns};
 use self::symtable::Symtable;
 use self::variables::var_decl;
 use crate::file_cache::{FileCache, ResolvedFile};
+use crate::sema::unused_variable::{check_unused_events, check_unused_namespace_variables};
 
 pub type ArrayDimension = Option<(pt::Loc, BigInt)>;
 
@@ -39,9 +41,16 @@ pub const SOLANA_BUCKET_SIZE: u64 = 251;
 pub const SOLANA_FIRST_OFFSET: u64 = 16;
 pub const SOLANA_SPARSE_ARRAY_SIZE: u64 = 1024;
 
+/// Performs semantic analysis and checks for unused variables
+pub fn sema(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
+    perform_sema(file, cache, ns);
+    check_unused_namespace_variables(ns);
+    check_unused_events(ns);
+}
+
 /// Load a file file from the cache, parse and resolve it. The file must be present in
 /// the cache. This function is recursive for imports.
-pub fn sema(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
+pub fn perform_sema(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
     let file_no = ns.files.len();
 
     let source_code = cache.get_file_contents(&file.full_path);
@@ -168,7 +177,7 @@ fn resolve_import(
         }
         Ok(file) => {
             if !ns.files.iter().any(|f| *f == file.full_path) {
-                sema(file.clone(), cache, ns);
+                perform_sema(file.clone(), cache, ns);
 
                 // give up if we failed
                 if diagnostics::any_errors(&ns.diagnostics) {
@@ -1469,14 +1478,14 @@ impl ast::Namespace {
         expr: &pt::Expression,
         diagnostics: &mut Vec<ast::Diagnostic>,
     ) -> Result<ArrayDimension, ()> {
-        let symtable = Symtable::new();
+        let mut symtable = Symtable::new();
 
         let size_expr = expression(
             &expr,
             file_no,
             contract_no,
             self,
-            &symtable,
+            &mut symtable,
             true,
             diagnostics,
             Some(&ast::Type::Uint(256)),

+ 149 - 58
src/sema/statements.rs

@@ -6,6 +6,8 @@ use super::expression::{
 };
 use super::symtable::{LoopScopes, Symtable};
 use crate::parser::pt;
+use crate::sema::symtable::VariableUsage;
+use crate::sema::unused_variable::{check_function_call, used_variable};
 use std::collections::HashMap;
 
 pub fn resolve_function_body(
@@ -23,9 +25,13 @@ pub fn resolve_function_body(
     for (i, p) in def.params.iter().enumerate() {
         let p = p.1.as_ref().unwrap();
         if let Some(ref name) = p.name {
-            if let Some(pos) =
-                symtable.add(name, ns.functions[function_no].params[i].ty.clone(), ns)
-            {
+            if let Some(pos) = symtable.add(
+                name,
+                ns.functions[function_no].params[i].ty.clone(),
+                ns,
+                true,
+                VariableUsage::Parameter,
+            ) {
                 ns.check_shadowing(file_no, contract_no, name);
 
                 symtable.arguments.push(Some(pos));
@@ -74,9 +80,12 @@ pub fn resolve_function_body(
                                 base_no,
                                 contract_no,
                                 ns,
-                                &symtable,
+                                &mut symtable,
                                 &mut diagnostics,
                             ) {
+                                for arg in &args {
+                                    used_variable(ns, arg, &mut symtable);
+                                }
                                 ns.functions[function_no]
                                     .bases
                                     .insert(base_no, (base.loc, constructor_no, args));
@@ -152,7 +161,7 @@ pub fn resolve_function_body(
                     true,
                     contract_no,
                     ns,
-                    &symtable,
+                    &mut symtable,
                     &mut diagnostics,
                 ) {
                     modifiers.push(e);
@@ -176,9 +185,14 @@ pub fn resolve_function_body(
         if let Some(ref name) = p.1.as_ref().unwrap().name {
             return_required = false;
 
-            if let Some(pos) = symtable.add(name, ret.ty.clone(), ns) {
+            if let Some(pos) = symtable.add(
+                name,
+                ret.ty.clone(),
+                ns,
+                false,
+                VariableUsage::ReturnVariable,
+            ) {
                 ns.check_shadowing(file_no, contract_no, name);
-
                 symtable.returns.push(pos);
             }
         } else {
@@ -188,7 +202,15 @@ pub fn resolve_function_body(
                 name: "".to_owned(),
             };
 
-            let pos = symtable.add(&id, ret.ty.clone(), ns).unwrap();
+            let pos = symtable
+                .add(
+                    &id,
+                    ret.ty.clone(),
+                    ns,
+                    true,
+                    VariableUsage::AnonymousReturnVariable,
+                )
+                .unwrap();
 
             symtable.returns.push(pos);
         }
@@ -297,7 +319,9 @@ fn statement(
                 diagnostics,
             )?;
 
+            let mut initialized = false;
             let initializer = if let Some(init) = initializer {
+                initialized = true;
                 let expr = expression(
                     init,
                     file_no,
@@ -308,13 +332,20 @@ fn statement(
                     diagnostics,
                     Some(&var_ty),
                 )?;
+                used_variable(ns, &expr, symtable);
 
                 Some(cast(&expr.loc(), expr, &var_ty, true, ns, diagnostics)?)
             } else {
                 None
             };
 
-            if let Some(pos) = symtable.add(&decl.name, var_ty.clone(), ns) {
+            if let Some(pos) = symtable.add(
+                &decl.name,
+                var_ty.clone(),
+                ns,
+                initialized,
+                VariableUsage::StateVariable,
+            ) {
                 ns.check_shadowing(file_no, contract_no, &decl.name);
 
                 res.push(Statement::VariableDecl(
@@ -398,7 +429,7 @@ fn statement(
                 diagnostics,
                 Some(&Type::Bool),
             )?;
-
+            used_variable(ns, &expr, symtable);
             let cond = cast(&expr.loc(), expr, &Type::Bool, true, ns, diagnostics)?;
 
             symtable.new_scope();
@@ -433,7 +464,7 @@ fn statement(
                 diagnostics,
                 Some(&Type::Bool),
             )?;
-
+            used_variable(ns, &expr, symtable);
             let cond = cast(&expr.loc(), expr, &Type::Bool, true, ns, diagnostics)?;
 
             symtable.new_scope();
@@ -467,6 +498,7 @@ fn statement(
                 diagnostics,
                 Some(&Type::Bool),
             )?;
+            used_variable(ns, &expr, symtable);
 
             let cond = cast(&expr.loc(), expr, &Type::Bool, true, ns, diagnostics)?;
 
@@ -685,6 +717,11 @@ fn statement(
                 return Err(());
             }
 
+            for offset in symtable.returns.iter() {
+                let elem = symtable.vars.get_mut(offset).unwrap();
+                (*elem).assigned = true;
+            }
+
             res.push(Statement::Return(
                 *loc,
                 symtable
@@ -710,6 +747,15 @@ fn statement(
                 diagnostics,
             )?;
 
+            for offset in symtable.returns.iter() {
+                let elem = symtable.vars.get_mut(offset).unwrap();
+                (*elem).assigned = true;
+            }
+
+            for item in &vals {
+                used_variable(ns, item, symtable);
+            }
+
             res.push(Statement::Return(*loc, vals));
 
             Ok(false)
@@ -861,7 +907,8 @@ fn emit_event(
             let mut temp_diagnostics = Vec::new();
 
             for event_no in &event_nos {
-                let event = &ns.events[*event_no];
+                let event = &mut ns.events[*event_no];
+                event.used = true;
                 if args.len() != event.fields.len() {
                     temp_diagnostics.push(Diagnostic::error(
                         *loc,
@@ -897,6 +944,7 @@ fn emit_event(
                             break;
                         }
                     };
+                    used_variable(ns, &arg, symtable);
 
                     match cast(
                         &arg.loc(),
@@ -956,7 +1004,8 @@ fn emit_event(
             let mut temp_diagnostics = Vec::new();
 
             for event_no in &event_nos {
-                let event = &ns.events[*event_no];
+                let event = &mut ns.events[*event_no];
+                event.used = true;
                 let params_len = event.fields.len();
 
                 if params_len != arguments.len() {
@@ -1022,6 +1071,8 @@ fn emit_event(
                         }
                     };
 
+                    used_variable(ns, &arg, symtable);
+
                     match cast(&arg.loc(), arg, &param.ty, true, ns, &mut temp_diagnostics) {
                         Ok(expr) => cast_args.push(expr),
                         Err(_) => {
@@ -1156,7 +1207,13 @@ fn destructure(
                 let (ty, ty_loc) =
                     resolve_var_decl_ty(&ty, &storage, file_no, contract_no, ns, diagnostics)?;
 
-                if let Some(pos) = symtable.add(&name, ty.clone(), ns) {
+                if let Some(pos) = symtable.add(
+                    &name,
+                    ty.clone(),
+                    ns,
+                    true,
+                    VariableUsage::DestructureVariable,
+                ) {
                     ns.check_shadowing(file_no, contract_no, &name);
 
                     left_tys.push(Some(ty.clone()));
@@ -1559,27 +1616,37 @@ fn try_catch(
     }
 
     let fcall = match expr {
-        pt::Expression::FunctionCall(loc, ty, args) => function_call_expr(
-            loc,
-            ty,
-            args,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            false,
-            diagnostics,
-        )?,
-        pt::Expression::NamedFunctionCall(loc, ty, args) => named_function_call_expr(
-            loc,
-            ty,
-            args,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            diagnostics,
-        )?,
+        pt::Expression::FunctionCall(loc, ty, args) => {
+            let res = function_call_expr(
+                loc,
+                ty,
+                args,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                false,
+                diagnostics,
+            )?;
+            check_function_call(ns, &res, symtable);
+            res
+        }
+        pt::Expression::NamedFunctionCall(loc, ty, args) => {
+            let res = named_function_call_expr(
+                loc,
+                ty,
+                args,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                diagnostics,
+            )?;
+
+            check_function_call(ns, &res, symtable);
+
+            res
+        }
         pt::Expression::New(loc, call) => {
             let mut call = call.as_ref();
 
@@ -1598,26 +1665,36 @@ fn try_catch(
             }
 
             match call {
-                pt::Expression::FunctionCall(_, ty, args) => new(
-                    loc,
-                    ty,
-                    args,
-                    file_no,
-                    contract_no,
-                    ns,
-                    symtable,
-                    diagnostics,
-                )?,
-                pt::Expression::NamedFunctionCall(_, ty, args) => constructor_named_args(
-                    loc,
-                    ty,
-                    args,
-                    file_no,
-                    contract_no,
-                    ns,
-                    symtable,
-                    diagnostics,
-                )?,
+                pt::Expression::FunctionCall(_, ty, args) => {
+                    let res = new(
+                        loc,
+                        ty,
+                        args,
+                        file_no,
+                        contract_no,
+                        ns,
+                        symtable,
+                        diagnostics,
+                    )?;
+                    check_function_call(ns, &res, symtable);
+
+                    res
+                }
+                pt::Expression::NamedFunctionCall(_, ty, args) => {
+                    let res = constructor_named_args(
+                        loc,
+                        ty,
+                        args,
+                        file_no,
+                        contract_no,
+                        ns,
+                        symtable,
+                        diagnostics,
+                    )?;
+                    check_function_call(ns, &res, symtable);
+
+                    res
+                }
                 _ => unreachable!(),
             }
         }
@@ -1737,7 +1814,13 @@ fn try_catch(
                 }
 
                 if let Some(name) = name {
-                    if let Some(pos) = symtable.add(&name, ret_ty.clone(), ns) {
+                    if let Some(pos) = symtable.add(
+                        &name,
+                        ret_ty.clone(),
+                        ns,
+                        false,
+                        VariableUsage::TryCatchReturns,
+                    ) {
                         ns.check_shadowing(file_no, contract_no, &name);
                         params.push((
                             Some(pos),
@@ -1840,7 +1923,13 @@ fn try_catch(
         };
 
         if let Some(name) = &error_stmt.1.name {
-            if let Some(pos) = symtable.add(&name, Type::String, ns) {
+            if let Some(pos) = symtable.add(
+                &name,
+                Type::String,
+                ns,
+                true,
+                VariableUsage::TryCatchErrorString,
+            ) {
                 ns.check_shadowing(file_no, contract_no, &name);
 
                 error_pos = Some(pos);
@@ -1904,7 +1993,9 @@ fn try_catch(
     let mut catch_stmt_resolved = Vec::new();
 
     if let Some(name) = &catch_stmt.0.name {
-        if let Some(pos) = symtable.add(&name, catch_ty, ns) {
+        if let Some(pos) =
+            symtable.add(&name, catch_ty, ns, true, VariableUsage::TryCatchErrorBytes)
+        {
             ns.check_shadowing(file_no, contract_no, &name);
             catch_param_pos = Some(pos);
             catch_param.name = name.name.to_string();

+ 26 - 1
src/sema/symtable.rs

@@ -12,6 +12,21 @@ pub struct Variable {
     pub ty: Type,
     pub pos: usize,
     pub slice: bool,
+    pub assigned: bool,
+    pub read: bool,
+    pub usage_type: VariableUsage,
+}
+
+#[derive(Clone)]
+pub enum VariableUsage {
+    Parameter,
+    ReturnVariable,
+    AnonymousReturnVariable,
+    StateVariable,
+    DestructureVariable,
+    TryCatchReturns,
+    TryCatchErrorString,
+    TryCatchErrorBytes,
 }
 
 struct VarScope(HashMap<String, usize>, Option<HashSet<usize>>);
@@ -36,7 +51,14 @@ impl Symtable {
         }
     }
 
-    pub fn add(&mut self, id: &pt::Identifier, ty: Type, ns: &mut Namespace) -> Option<usize> {
+    pub fn add(
+        &mut self,
+        id: &pt::Identifier,
+        ty: Type,
+        ns: &mut Namespace,
+        assigned: bool,
+        usage_type: VariableUsage,
+    ) -> Option<usize> {
         let pos = ns.next_id;
         ns.next_id += 1;
 
@@ -47,6 +69,9 @@ impl Symtable {
                 ty,
                 pos,
                 slice: false,
+                assigned,
+                usage_type,
+                read: false,
             },
         );
 

+ 2 - 0
src/sema/types.rs

@@ -85,6 +85,7 @@ pub fn resolve_typenames<'a>(
                     fields: Vec::new(),
                     anonymous: def.anonymous,
                     signature: String::new(),
+                    used: false,
                 });
 
                 delay.events.push((pos, def, None));
@@ -279,6 +280,7 @@ fn resolve_contract<'a>(
                     fields: Vec::new(),
                     anonymous: s.anonymous,
                     signature: String::new(),
+                    used: false,
                 });
 
                 delay.events.push((pos, s, Some(contract_no)));

+ 366 - 0
src/sema/unused_variable.rs

@@ -0,0 +1,366 @@
+use crate::parser::pt::Loc;
+use crate::sema::ast::{
+    Builtin, Diagnostic, ErrorType, Expression, Level, Namespace, Note, Statement,
+};
+use crate::sema::symtable::{Symtable, VariableUsage};
+use crate::sema::{ast, symtable};
+
+/// Mark variables as assigned, either in the symbol table (for state variables) or in the
+/// Namespace (for storage variables)
+pub fn assigned_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
+    match &exp {
+        Expression::StorageVariable(_, _, contract_no, offset) => {
+            ns.contracts[*contract_no].variables[*offset].assigned = true;
+        }
+
+        Expression::Variable(_, _, offset) => {
+            let var = symtable.vars.get_mut(offset).unwrap();
+            (*var).assigned = true;
+        }
+
+        Expression::StructMember(_, _, str, _) => {
+            assigned_variable(ns, str, symtable);
+        }
+
+        Expression::Subscript(_, _, array, index)
+        | Expression::DynamicArraySubscript(_, _, array, index)
+        | Expression::StorageBytesSubscript(_, array, index) => {
+            assigned_variable(ns, array, symtable);
+            used_variable(ns, index, symtable);
+        }
+
+        _ => {}
+    }
+}
+
+/// Mark variables as used, either in the symbol table (for state variables) or in the
+/// Namespace (for global constants and storage variables)
+/// The functions handles complex expressions in a recursive fashion, such as array length call,
+/// assign expressions and array subscripts.
+pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
+    match &exp {
+        Expression::StorageVariable(_, _, contract_no, offset) => {
+            ns.contracts[*contract_no].variables[*offset].read = true;
+        }
+
+        Expression::Variable(_, _, offset) => {
+            let var = symtable.vars.get_mut(offset).unwrap();
+            (*var).read = true;
+        }
+
+        Expression::ConstantVariable(_, _, Some(contract_no), offset) => {
+            ns.contracts[*contract_no].variables[*offset].read = true;
+        }
+
+        Expression::ConstantVariable(_, _, None, offset) => {
+            ns.constants[*offset].read = true;
+        }
+
+        Expression::ZeroExt(_, _, variable) => {
+            used_variable(ns, variable, symtable);
+        }
+
+        Expression::StructMember(_, _, str, _) => {
+            used_variable(ns, str, symtable);
+        }
+
+        Expression::Subscript(_, _, array, index)
+        | Expression::DynamicArraySubscript(_, _, array, index)
+        | Expression::StorageBytesSubscript(_, array, index) => {
+            used_variable(ns, array, symtable);
+            used_variable(ns, index, symtable);
+        }
+
+        Expression::Assign(_, _, assigned, assignee) => {
+            assigned_variable(ns, assigned, symtable);
+            used_variable(ns, assignee, symtable);
+        }
+
+        Expression::DynamicArrayLength(_, array) => {
+            used_variable(ns, array, symtable);
+        }
+
+        Expression::StorageArrayLength {
+            loc: _,
+            ty: _,
+            array,
+            ..
+        } => {
+            used_variable(ns, array, symtable);
+        }
+
+        Expression::StorageLoad(_, _, expr) => {
+            used_variable(ns, expr, symtable);
+        }
+
+        _ => {}
+    }
+}
+
+/// Mark function arguments as used. If the function is an attribute of another variable, mark the
+/// usage of the latter as well
+pub fn check_function_call(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
+    match &exp {
+        Expression::InternalFunctionCall {
+            loc: _,
+            returns: _,
+            function,
+            args,
+        }
+        | Expression::ExternalFunctionCall {
+            loc: _,
+            returns: _,
+            function,
+            args,
+            ..
+        } => {
+            for arg in args {
+                used_variable(ns, arg, symtable);
+            }
+            check_function_call(ns, function, symtable);
+        }
+
+        Expression::Constructor {
+            loc: _,
+            contract_no: _,
+            constructor_no: _,
+            args,
+            ..
+        } => {
+            for arg in args {
+                used_variable(ns, arg, symtable);
+            }
+        }
+
+        Expression::ExternalFunctionCallRaw {
+            loc: _,
+            ty: _,
+            address: function,
+            args,
+            ..
+        } => {
+            used_variable(ns, args, symtable);
+            check_function_call(ns, function, symtable);
+        }
+
+        Expression::ExternalFunction {
+            loc: _,
+            ty: _,
+            address,
+            function_no,
+        } => {
+            used_variable(ns, address, symtable);
+            if ns.functions[*function_no].is_accessor {
+                let body = ns.functions[*function_no].body[0].clone();
+                if let Statement::Return(_, exprs) = &body {
+                    used_variable(ns, &exprs[0], symtable);
+                }
+            }
+        }
+
+        Expression::Builtin(_, _, expr_type, args) => match expr_type {
+            Builtin::ArrayPush => {
+                assigned_variable(ns, &args[0], symtable);
+                if args.len() > 1 {
+                    used_variable(ns, &args[1], symtable);
+                }
+            }
+
+            Builtin::ArrayPop => {
+                used_variable(ns, &args[0], symtable);
+            }
+            _ => {}
+        },
+
+        Expression::DynamicArrayPush(_, array, _, arg) => {
+            assigned_variable(ns, array, symtable);
+            used_variable(ns, arg, symtable);
+        }
+
+        Expression::DynamicArrayPop(_, array, _) => {
+            used_variable(ns, array, symtable);
+        }
+
+        _ => {}
+    }
+}
+
+/// Marks as used variables that appear in an expression with right and left hand side.
+pub fn check_var_usage_expression(
+    ns: &mut Namespace,
+    left: &Expression,
+    right: &Expression,
+    symtable: &mut Symtable,
+) {
+    used_variable(ns, left, symtable);
+    used_variable(ns, right, symtable);
+}
+
+/// Generate warnings for unused varibles
+fn generate_unused_warning(loc: Loc, text: &String, notes: Vec<Note>) -> Box<Diagnostic> {
+    Box::new(Diagnostic {
+        level: Level::Warning,
+        ty: ErrorType::Warning,
+        pos: Some(loc),
+        message: text.clone(),
+        notes,
+    })
+}
+
+/// Emit different warning types according to the state variable usage
+pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<Diagnostic>> {
+    match &variable.usage_type {
+        VariableUsage::Parameter => {
+            if !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "Function parameter '{}' has never been used.",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+            None
+        }
+
+        VariableUsage::ReturnVariable => {
+            if !variable.assigned {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "Return variable '{}' has never been assigned",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+            None
+        }
+
+        VariableUsage::StateVariable => {
+            if !variable.assigned && !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "State variable '{}' has never been read nor assigned",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            } else if variable.assigned && !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "State variable '{}' has been assigned, but never read.",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            } else if !variable.assigned && variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "State variable '{}' has never been assigned a value, but has been read.",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+            None
+        }
+
+        VariableUsage::DestructureVariable => {
+            if !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "Destructure variable '{}' has never been used.",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+
+            None
+        }
+
+        VariableUsage::TryCatchReturns => {
+            if !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "Try catch returns variable '{}' has never been read.",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+
+            None
+        }
+
+        VariableUsage::AnonymousReturnVariable
+        | VariableUsage::TryCatchErrorBytes
+        | VariableUsage::TryCatchErrorString => None,
+    }
+}
+
+/// Emit warnings depending on the state variable usage
+fn emit_warning_contract_variables(variable: &ast::Variable) -> Option<Box<Diagnostic>> {
+    if !variable.assigned && !variable.read {
+        return Some(generate_unused_warning(
+            variable.loc,
+            &format!(
+                "Storage variable '{}' has been assigned, but never used.",
+                variable.name
+            ),
+            vec![],
+        ));
+    } else if variable.assigned && !variable.read {
+        return Some(generate_unused_warning(
+            variable.loc,
+            &format!("Storage variable '{}' has never been used.", variable.name),
+            vec![],
+        ));
+    }
+    //Solidity attributes zero value to contract values that have never been assigned
+    //There is no need to raise warning if we use them, as they have a valid value.
+
+    None
+}
+
+/// Check for unused constants and storage variables
+pub fn check_unused_namespace_variables(ns: &mut Namespace) {
+    for contract in &ns.contracts {
+        for variable in &contract.variables {
+            if let Some(warning) = emit_warning_contract_variables(variable) {
+                ns.diagnostics.push(*warning);
+            }
+        }
+    }
+
+    //Global constants should have been initialized during declaration
+    for constant in &ns.constants {
+        if !constant.read {
+            ns.diagnostics.push(*generate_unused_warning(
+                constant.loc,
+                &format!("Global constant '{}' has never been used.", constant.name),
+                vec![],
+            ))
+        }
+    }
+}
+
+/// Check for unused events
+pub fn check_unused_events(ns: &mut Namespace) {
+    for event in &ns.events {
+        if !event.used {
+            ns.diagnostics.push(*generate_unused_warning(
+                event.loc,
+                &format!("Event '{}' has never been emitted.", event.name),
+                vec![],
+            ))
+        }
+    }
+}

+ 5 - 1
src/sema/variables.rs

@@ -98,6 +98,7 @@ pub fn var_decl(
 
     let mut is_constant = false;
     let mut visibility: Option<pt::Visibility> = None;
+    let mut initialized = false;
 
     for attr in attrs {
         match &attr {
@@ -178,13 +179,14 @@ pub fn var_decl(
 
     let initializer = if let Some(initializer) = &s.initializer {
         let mut diagnostics = Vec::new();
+        initialized = true;
 
         let res = match expression(
             &initializer,
             file_no,
             contract_no,
             ns,
-            &symtable,
+            symtable,
             is_constant,
             &mut diagnostics,
             Some(&ty),
@@ -250,6 +252,8 @@ pub fn var_decl(
         ty: ty.clone(),
         constant: is_constant,
         initializer,
+        read: false,
+        assigned: initialized,
     };
 
     let pos = if let Some(contract_no) = contract_no {

+ 7 - 4
tests/substrate_tests/tags.rs

@@ -184,7 +184,8 @@ fn event_tag() {
         Target::Substrate,
     );
 
-    assert_eq!(ns.diagnostics.len(), 0);
+    //Event never emitted generates a warning
+    assert_eq!(ns.diagnostics.len(), 1);
 
     assert_eq!(ns.events[0].tags[0].tag, "param");
     assert_eq!(ns.events[0].tags[0].value, "asdad");
@@ -208,7 +209,8 @@ fn event_tag() {
         Target::Substrate,
     );
 
-    assert_eq!(ns.diagnostics.len(), 1);
+    //Event never emitted generates a warning
+    assert_eq!(ns.diagnostics.len(), 2);
 
     assert_eq!(ns.events[0].tags[0].tag, "title");
     assert_eq!(ns.events[0].tags[0].value, "foo bar");
@@ -399,7 +401,7 @@ fn functions() {
         Target::Substrate,
     );
 
-    assert_eq!(ns.diagnostics.len(), 2);
+    assert_eq!(ns.diagnostics.len(), 3);
 
     assert_eq!(ns.functions[0].tags[0].tag, "param");
     assert_eq!(ns.functions[0].tags[0].value, "sadad");
@@ -441,7 +443,8 @@ fn variables() {
         Target::Substrate,
     );
 
-    assert_eq!(ns.diagnostics.len(), 2);
+    //Variable 'y' has never been used (one item error in diagnostic)
+    assert_eq!(ns.diagnostics.len(), 3);
 
     assert_eq!(ns.contracts[0].variables[0].tags[0].tag, "notice");
     assert_eq!(ns.contracts[0].variables[0].tags[0].value, "so here we are");