//===--- Scope.h - Declarations for scope RAII objects ----------*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
// This file defines the Scope and FullExpr RAII objects.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_SILGEN_SCOPE_H
#define SWIFT_SILGEN_SCOPE_H

#include "SILGenFunction.h"
#include "swift/SIL/SILDebugScope.h"
#include "Cleanup.h"

namespace swift {
namespace Lowering {

/// A Scope is a RAII object recording that a scope (e.g. a brace
/// statement) has been entered.
class LLVM_LIBRARY_VISIBILITY Scope {
  CleanupManager &cleanups;
  CleanupsDepth depth;
  Scope *savedInnermostScope;
  CleanupLocation loc;

  friend class CleanupManager;

public:
  explicit Scope(CleanupManager &cleanups, CleanupLocation loc)
      : cleanups(cleanups), depth(cleanups.getCleanupsDepth()),
        savedInnermostScope(cleanups.innermostScope), loc(loc) {
    assert(depth.isValid());
    cleanups.innermostScope = this;
    if (savedInnermostScope)
      cleanups.stack.checkIterator(savedInnermostScope->depth);
  }

  Scope(const Scope &other) = delete;
  Scope &operator=(const Scope &other) = delete;

  Scope(Scope &&other)
      : cleanups(other.cleanups), depth(other.depth),
        savedInnermostScope(other.savedInnermostScope), loc(other.loc) {
    // Invalidate other.
    other.depth = CleanupsDepth::invalid();
  }

  Scope &operator=(Scope &&other) {
    depth = other.depth;
    savedInnermostScope = other.savedInnermostScope;
    loc = other.loc;

    // Invalidate other.
    other.depth = CleanupsDepth::invalid();

    return *this;
  }

  explicit Scope(SILGenFunction &SGF, SILLocation loc)
      : Scope(SGF.Cleanups, CleanupLocation(loc)) {}

  void pop() {
    assert(depth.isValid() && "popping a scope twice!");
    popImpl();
    depth = CleanupsDepth::invalid();
  }
  
  ~Scope() {
    if (depth.isValid())
      popImpl();
  }

  /// Verify that the invariants of this scope still hold.
  void verify();

  bool isValid() const { return depth.isValid(); }

  /// Pop the scope pushing the +1 ManagedValue through the scope. Asserts if mv
  /// is a plus zero managed value.
  ManagedValue popPreservingValue(ManagedValue mv);

  /// Pop this scope pushing the +1 rvalue through the scope. Asserts if rv is a
  /// plus zero rvalue.
  RValue popPreservingValue(RValue &&rv);

private:
  /// Internal private implementation of popImpl so we can use it in Scope::pop
  /// and in Scope's destructor.
  void popImpl();
};

/// A scope that must be manually popped by the using code. If not
/// popped, the destructor asserts.
class LLVM_LIBRARY_VISIBILITY AssertingManualScope {
  Scope scope;

public:
  explicit AssertingManualScope(CleanupManager &cleanups, CleanupLocation loc)
      : scope(cleanups, loc) {}

  AssertingManualScope(AssertingManualScope &&other)
      : scope(std::move(other.scope)) {}

  AssertingManualScope &operator=(AssertingManualScope &&other) {
    scope = std::move(other.scope);
    return *this;
  }

  ~AssertingManualScope() {
    assert(!scope.isValid() && "Unpopped manual scope?!");
  }

  void pop() && { scope.pop(); }
};

/// A FullExpr is a RAII object recording that a full-expression has
/// been entered.  A full-expression is essentially a very small scope
/// for the temporaries in an expression, with the added complexity
/// that (eventually, very likely) we have to deal with expressions
/// that are only conditionally evaluated.
class LLVM_LIBRARY_VISIBILITY FullExpr : private Scope {
public:
  explicit FullExpr(CleanupManager &cleanups, CleanupLocation loc)
      : Scope(cleanups, loc) {}
  using Scope::pop;
};

/// A LexicalScope is a Scope that is also exposed to the debug info.
class LLVM_LIBRARY_VISIBILITY LexicalScope : private Scope {
  SILGenFunction& SGF;
public:
  explicit LexicalScope(SILGenFunction &SGF, CleanupLocation loc)
      : Scope(SGF.Cleanups, loc), SGF(SGF) {
    SGF.enterDebugScope(loc);
  }
  using Scope::pop;

  ~LexicalScope() {
    SGF.leaveDebugScope();
  }
};

/// A scope that only exists in the debug info.
class LLVM_LIBRARY_VISIBILITY DebugScope {
  SILGenFunction &SGF;

public:
  explicit DebugScope(SILGenFunction &SGF, CleanupLocation loc) : SGF(SGF) {
    SGF.enterDebugScope(loc);
  }

  ~DebugScope() { SGF.leaveDebugScope(); }
};

} // end namespace Lowering
} // end namespace swift

#endif
