/*
    This file is part of TON Blockchain source code.
    TON Blockchain is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.
    TON Blockchain is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with TON Blockchain.  If not, see .
*/
#include "tolk.h"
#include "ast.h"
#include "ast-visitor.h"
#include "platform-utils.h"
/*
 *   This pipe checks lvalue/rvalue for validity.
 *   It happens after type inferring (after methods binding) and after lvalue/rvalue are refined based on fun_ref.
 *
 *   Example: `f() = 4`, `f()` was earlier marked as lvalue, it's incorrect.
 *   Example: `f(mutate 5)`, `5` was marked also, it's incorrect.
 */
namespace tolk {
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_cannot_be_used_as_lvalue(AnyV v, const std::string& details) {
  // example: `f() = 32`
  // example: `loadUint(c.beginParse(), 32)` (since `loadUint()` mutates the first argument)
  v->error(details + " can not be used as lvalue");
}
GNU_ATTRIBUTE_NORETURN GNU_ATTRIBUTE_COLD
static void fire_error_modifying_immutable_variable(AnyExprV v, const LocalVarData* var_ref) {
  if (var_ref->param_idx == 0 && var_ref->name == "self") {
    v->error("modifying `self`, which is immutable by default; probably, you want to declare `mutate self`");
  } else {
    v->error("modifying immutable variable `" + var_ref->name + "`");
  }
}
// validate a function used as rvalue, like `var cb = f`
// it's not a generic function (ensured earlier at type inferring) and has some more restrictions
static void validate_function_used_as_noncall(AnyExprV v, const FunctionData* fun_ref) {
  if (!fun_ref->arg_order.empty() || !fun_ref->ret_order.empty()) {
    v->error("saving `" + fun_ref->name + "` into a variable will most likely lead to invalid usage, since it changes the order of variables on the stack");
  }
  if (fun_ref->has_mutate_params()) {
    v->error("saving `" + fun_ref->name + "` into a variable is impossible, since it has `mutate` parameters and thus can only be called directly");
  }
}
class CheckRValueLvalueVisitor final : public ASTVisitorFunctionBody {
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "assignment");
    }
    parent::visit(v);
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "assignment");
    }
    parent::visit(v);
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "operator " + static_cast(v->operator_name));
    }
    parent::visit(v);
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "operator " + static_cast(v->operator_name));
    }
    parent::visit(v);
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "operator ?:");
    }
    parent::visit(v);
  }
  void visit(V v) override {
    // if `x as int` is lvalue, then `x` is also lvalue, so check that `x` is ok
    parent::visit(v->get_expr());
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "literal");
    }
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "literal");
    }
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "literal");
    }
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "literal");
    }
  }
  void visit(V v) override {
    // a reference to a method used as rvalue, like `var v = t.tupleAt`
    if (v->is_rvalue && v->is_target_fun_ref()) {
      validate_function_used_as_noncall(v, std::get(v->target));
    }
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      fire_error_cannot_be_used_as_lvalue(v, "function call");
    }
    if (!v->fun_maybe) {
      parent::visit(v->get_callee());
    }
    // for `f()` don't visit ast_reference `f`, to detect `f` usage as non-call, like `var cb = f`
    // same for `obj.method()`, don't visit ast_reference method, visit only obj
    if (v->is_dot_call()) {
      parent::visit(v->get_dot_obj());
    }
    for (int i = 0; i < v->get_num_args(); ++i) {
      parent::visit(v->get_arg(i));
    }
  }
  void visit(V v) override {
    if (v->marked_as_redef) {
      tolk_assert(v->var_ref);
      if (v->var_ref->is_immutable()) {
        v->error("`redef` for immutable variable");
      }
    }
  }
  void visit(V v) override {
    if (v->is_lvalue) {
      tolk_assert(v->sym);
      if (const auto* var_ref = v->sym->try_as(); var_ref && var_ref->is_immutable()) {
        fire_error_modifying_immutable_variable(v, var_ref);
      } else if (v->sym->try_as()) {
        v->error("modifying immutable constant");
      } else if (v->sym->try_as()) {
        v->error("function can't be used as lvalue");
      }
    }
    // a reference to a function used as rvalue, like `var v = someFunction`
    if (const FunctionData* fun_ref = v->sym->try_as(); fun_ref && v->is_rvalue) {
      validate_function_used_as_noncall(v, fun_ref);
    }
  }
  void visit(V v) override {
    if (v->is_rvalue) {
      v->error("`_` can't be used as a value; it's a placeholder for a left side of assignment");
    }
  }
  void visit(V v) override {
    parent::visit(v->get_try_body());
    // skip catch(_,excNo), there are always vars due to grammar, lvalue/rvalue aren't set to them
    parent::visit(v->get_catch_body());
  }
public:
  bool should_visit_function(const FunctionData* fun_ref) override {
    return fun_ref->is_code_function() && !fun_ref->is_generic_function();
  }
};
void pipeline_check_rvalue_lvalue() {
  visit_ast_of_all_functions();
}
} // namespace tolk