// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "gn/template.h"

#include <memory>
#include <utility>

#include "gn/err.h"
#include "gn/functions.h"
#include "gn/parse_tree.h"
#include "gn/scope.h"
#include "gn/scope_per_file_provider.h"
#include "gn/settings.h"
#include "gn/trace.h"
#include "gn/value.h"
#include "gn/variables.h"

Template::Template(const Scope* scope, const FunctionCallNode* def)
    : closure_(scope->MakeClosure()), definition_(def) {}

Template::Template(std::unique_ptr<Scope> scope, const FunctionCallNode* def)
    : closure_(std::move(scope)), definition_(def) {}

Template::~Template() = default;

Value Template::Invoke(Scope* scope,
                       const FunctionCallNode* invocation,
                       const std::string& template_name,
                       const std::vector<Value>& args,
                       BlockNode* block,
                       Err* err) const {
  // Don't allow templates to be executed from imported files. Imports are for
  // simple values only.
  if (!EnsureNotProcessingImport(invocation, scope, err))
    return Value();

  ScopedTrace trace(TraceItem::TRACE_FILE_EXECUTE_TEMPLATE, template_name);
  trace.SetToolchain(scope->settings()->toolchain_label());

  // First run the invocation's block. Need to allocate the scope on the heap
  // so we can pass ownership to the template.
  std::unique_ptr<Scope> invocation_scope = std::make_unique<Scope>(scope);
  if (!FillTargetBlockScope(scope, invocation, template_name, block, args,
                            invocation_scope.get(), err))
    return Value();

  {
    // Don't allow the block of the template invocation to include other
    // targets configs, or template invocations. This must only be applied
    // to the invoker's block rather than the whole function because the
    // template execution itself must be able to define targets, etc.
    NonNestableBlock non_nestable(scope, invocation, "template invocation");
    if (!non_nestable.Enter(err))
      return Value();

    block->Execute(invocation_scope.get(), err);
    if (err->has_error())
      return Value();
  }

  // Set up the scope to run the template and set the current directory for the
  // template (which ScopePerFileProvider uses to base the target-related
  // variables target_gen_dir and target_out_dir on) to be that of the invoker.
  // This way, files don't have to be rebased and target_*_dir works the way
  // people expect (otherwise its to easy to be putting generated files in the
  // gen dir corresponding to an imported file).
  Scope template_scope(closure_.get());
  template_scope.set_source_dir(scope->GetSourceDir());

  // Track the invocation of the template on the template's scope
  template_scope.SetTemplateInvocationEntry(
      template_name, args[0].string_value(), invocation->GetRange().begin());

  // Propagate build dependency files from invoker scope (template scope already
  // propagated via parent scope).
  template_scope.AddBuildDependencyFiles(
      invocation_scope->build_dependency_files());

  ScopePerFileProvider per_file_provider(&template_scope, true);

  // Targets defined in the template go in the collector for the invoking file.
  template_scope.set_item_collector(scope->GetItemCollector());

  // We jump through some hoops to avoid copying the invocation scope when
  // setting it in the template scope (since the invocation scope may have
  // large lists of source files in it and could be expensive to copy).
  //
  // Scope.SetValue will copy the value which will in turn copy the scope, but
  // if we instead create a value and then set the scope on it, the copy can
  // be avoided.
  template_scope.SetValue(variables::kInvoker,
                          Value(nullptr, std::unique_ptr<Scope>()), invocation);
  Value* invoker_value = template_scope.GetMutableValue(
      variables::kInvoker, Scope::SEARCH_NESTED, false);
  invoker_value->SetScopeValue(std::move(invocation_scope));
  template_scope.set_source_dir(scope->GetSourceDir());

  const std::string_view target_name(variables::kTargetName);
  template_scope.SetValue(
      target_name, Value(invocation, args[0].string_value()), invocation);

  // Actually run the template code.
  Value result = definition_->block()->Execute(&template_scope, err);
  if (err->has_error()) {
    // If there was an error, append the caller location so the error message
    // displays a stack trace of how it got here.
    err->AppendSubErr(Err(invocation, "whence it was called."));
    return Value();
  }

  // Check for unused variables in the invocation scope. This will find typos
  // of things the caller meant to pass to the template but the template didn't
  // read out.
  //
  // This is a bit tricky because it's theoretically possible for the template
  // to overwrite the value of "invoker" and free the Scope owned by the
  // value. So we need to look it up again and don't do anything if it doesn't
  // exist.
  invoker_value = template_scope.GetMutableValue(variables::kInvoker,
                                                 Scope::SEARCH_NESTED, false);
  if (invoker_value && invoker_value->type() == Value::SCOPE) {
    if (!invoker_value->scope_value()->CheckForUnusedVars(err)) {
      // If there was an error, append the caller location so the error message
      // displays a stack trace of how it got here.
      err->AppendSubErr(Err(invocation, "whence it was called."));
      return Value();
    }
  }

  // Check for unused variables in the template itself.
  if (!template_scope.CheckForUnusedVars(err)) {
    // If there was an error, append the caller location so the error message
    // displays a stack trace of how it got here.
    err->AppendSubErr(Err(invocation, "whence it was called."));
    return Value();
  }

  return result;
}

LocationRange Template::GetDefinitionRange() const {
  return definition_->GetRange();
}
