//===--- ForeignErrorConvention.h - Error conventions -----------*- 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 ForeignErrorConvention structure, which
// describes the rules for how to detect that a foreign API returns an
// error.
//
//===----------------------------------------------------------------------===//

#ifndef SWIFT_FOREIGN_ERROR_CONVENTION_H
#define SWIFT_FOREIGN_ERROR_CONVENTION_H

#include "swift/AST/Type.h"

namespace swift {

/// A small structure describing the error convention of a foreign declaration.
class ForeignErrorConvention {
public:
  enum Kind {
    /// An error is indicated by a zero result.  The function has
    /// been imported as returning Void.
    ///
    /// This convention provides a result type, which has a single
    /// field of integer type.
    ZeroResult,

    /// An error is indicated by a non-zero result.  The function has
    /// been imported as returning Void.
    ///
    /// This convention provides a result type, which has a single
    /// field of integer type.
    NonZeroResult,

    /// An error is indicated by a zero result, but the rest of the
    /// result is meaningful.
    ZeroPreservedResult,

    /// An error is indicated by a nil result.  The function has been
    /// imported as returning a non-optional type (which is not
    /// address-only).
    NilResult,

    /// An error is indicated by a non-nil error value being left in
    /// the error parameter.
    NonNilError,
  };

  /// Is the error owned by the caller, given that it exists?
  enum IsOwned_t : bool {
    IsNotOwned = false, IsOwned = true
  };

  /// Was the error parameter replaced by () or just removed?
  enum IsReplaced_t : bool {
    IsNotReplaced = false, IsReplaced = true
  };

  struct Info {
    unsigned TheKind : 8;
    unsigned ErrorIsOwned : 1;
    unsigned ErrorParameterIsReplaced : 1;
    unsigned ErrorParameterIndex : 22;

    Info(Kind kind, unsigned parameterIndex, IsOwned_t isOwned,
         IsReplaced_t isReplaced)
        : TheKind(unsigned(kind)), ErrorIsOwned(bool(isOwned)),
          ErrorParameterIsReplaced(bool(isReplaced)),
          ErrorParameterIndex(parameterIndex) {
      assert(parameterIndex == ErrorParameterIndex &&
             "parameter index overflowed");
    }

    Info() = default;
  };

private:  
  Info info;

  /// The error parameter type.  This is currently assumed to be an
  /// indirect out-parameter.
  CanType ErrorParameterType;

  /// The result type.  This is meaningful only for ZeroResult and
  /// NonZeroResult.
  CanType ResultType;

  ForeignErrorConvention(Kind kind, unsigned parameterIndex, IsOwned_t isOwned,
                         IsReplaced_t isReplaced, Type parameterType,
                         Type resultType = Type())
      : info(kind, parameterIndex, isOwned, isReplaced),
        ErrorParameterType(parameterType), ResultType(resultType) {}

public:
  static ForeignErrorConvention getZeroResult(unsigned parameterIndex,
                                              IsOwned_t isOwned,
                                              IsReplaced_t isReplaced,
                                              CanType parameterType,
                                              CanType resultType) {
    return { ZeroResult, parameterIndex, isOwned, isReplaced,
             parameterType, resultType };
  }

  static ForeignErrorConvention getNonZeroResult(unsigned parameterIndex,
                                                 IsOwned_t isOwned,
                                                 IsReplaced_t isReplaced,
                                                 CanType parameterType,
                                                 CanType resultType) {
    return { NonZeroResult, parameterIndex, isOwned, isReplaced,
             parameterType, resultType };
  }

  static ForeignErrorConvention getZeroPreservedResult(unsigned parameterIndex,
                                                       IsOwned_t isOwned,
                                                       IsReplaced_t isReplaced,
                                                       CanType parameterType) {
    return { ZeroPreservedResult, parameterIndex, isOwned, isReplaced,
             parameterType };
  }

  static ForeignErrorConvention getNilResult(unsigned parameterIndex,
                                             IsOwned_t isOwned,
                                             IsReplaced_t isReplaced,
                                             CanType parameterType) {
    return { NilResult, parameterIndex, isOwned, isReplaced, parameterType };
  }

  static ForeignErrorConvention getNonNilError(unsigned parameterIndex,
                                               IsOwned_t isOwned,
                                               IsReplaced_t isReplaced,
                                               CanType parameterType) {
    return { NonNilError, parameterIndex, isOwned, isReplaced, parameterType };
  }

  /// Returns the error convention in use.
  Kind getKind() const {
    return Kind(info.TheKind);
  }

  /// Returns true if this convention strips a layer of optionality
  /// from the formal result type.
  bool stripsResultOptionality() const {
    return getKind() == Kind::NilResult;
  }

  /// Returns the index of the error parameter.
  unsigned getErrorParameterIndex() const {
    return info.ErrorParameterIndex;
  }

  /// Has the error parameter been replaced with void?
  IsReplaced_t isErrorParameterReplacedWithVoid() const {
    return IsReplaced_t(info.ErrorParameterIsReplaced);
  }

  /// Returns whether the error result is owned.  It's assumed that the
  /// error parameter should be ignored if no error is present (unless
  /// it needs to be checked explicitly to determine that).
  IsOwned_t isErrorOwned() const {
    return IsOwned_t(info.ErrorIsOwned);
  }

  /// Returns the type of the error parameter.  Assumed to be an
  /// UnsafeMutablePointer<T?> for some T.
  CanType getErrorParameterType() const {
    return ErrorParameterType;
  }

  /// Returns the physical result type of the function, for functions
  /// that completely erase this information.
  CanType getResultType() const {
    assert(getKind() == ZeroResult ||
           getKind() == NonZeroResult);
    return ResultType;
  }
};

}

#endif
