//===-- APINotesYAMLCompiler.cpp - API Notes YAML Format Reader -*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// The types defined locally are designed to represent the YAML state, which
// adds an additional bit of state: e.g. a tri-state boolean attribute (yes, no,
// not applied) becomes a tri-state boolean + present.  As a result, while these
// enumerations appear to be redefining constants from the attributes table
// data, they are distinct.
//

#include "clang/APINotes/APINotesYAMLCompiler.h"
#include "clang/APINotes/Types.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/Specifiers.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/VersionTuple.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
#include <vector>
using namespace clang;
using namespace api_notes;

namespace {
enum class APIAvailability {
  Available = 0,
  OSX,
  IOS,
  None,
  NonSwift,
};
} // namespace

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<APIAvailability> {
  static void enumeration(IO &IO, APIAvailability &AA) {
    IO.enumCase(AA, "OSX", APIAvailability::OSX);
    IO.enumCase(AA, "iOS", APIAvailability::IOS);
    IO.enumCase(AA, "none", APIAvailability::None);
    IO.enumCase(AA, "nonswift", APIAvailability::NonSwift);
    IO.enumCase(AA, "available", APIAvailability::Available);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
enum class MethodKind {
  Class,
  Instance,
};
} // namespace

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<MethodKind> {
  static void enumeration(IO &IO, MethodKind &MK) {
    IO.enumCase(MK, "Class", MethodKind::Class);
    IO.enumCase(MK, "Instance", MethodKind::Instance);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Param {
  unsigned Position;
  Optional<bool> NoEscape = false;
  Optional<NullabilityKind> Nullability;
  Optional<RetainCountConventionKind> RetainCountConvention;
  StringRef Type;
};

typedef std::vector<Param> ParamsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Param)
LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(NullabilityKind)

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<NullabilityKind> {
  static void enumeration(IO &IO, NullabilityKind &NK) {
    IO.enumCase(NK, "Nonnull", NullabilityKind::NonNull);
    IO.enumCase(NK, "Optional", NullabilityKind::Nullable);
    IO.enumCase(NK, "Unspecified", NullabilityKind::Unspecified);
    IO.enumCase(NK, "NullableResult", NullabilityKind::NullableResult);
    // TODO: Mapping this to it's own value would allow for better cross
    // checking. Also the default should be Unknown.
    IO.enumCase(NK, "Scalar", NullabilityKind::Unspecified);

    // Aliases for compatibility with existing APINotes.
    IO.enumCase(NK, "N", NullabilityKind::NonNull);
    IO.enumCase(NK, "O", NullabilityKind::Nullable);
    IO.enumCase(NK, "U", NullabilityKind::Unspecified);
    IO.enumCase(NK, "S", NullabilityKind::Unspecified);
  }
};

template <> struct ScalarEnumerationTraits<RetainCountConventionKind> {
  static void enumeration(IO &IO, RetainCountConventionKind &RCCK) {
    IO.enumCase(RCCK, "none", RetainCountConventionKind::None);
    IO.enumCase(RCCK, "CFReturnsRetained",
                RetainCountConventionKind::CFReturnsRetained);
    IO.enumCase(RCCK, "CFReturnsNotRetained",
                RetainCountConventionKind::CFReturnsNotRetained);
    IO.enumCase(RCCK, "NSReturnsRetained",
                RetainCountConventionKind::NSReturnsRetained);
    IO.enumCase(RCCK, "NSReturnsNotRetained",
                RetainCountConventionKind::NSReturnsNotRetained);
  }
};

template <> struct MappingTraits<Param> {
  static void mapping(IO &IO, Param &P) {
    IO.mapRequired("Position", P.Position);
    IO.mapOptional("Nullability", P.Nullability, llvm::None);
    IO.mapOptional("RetainCountConvention", P.RetainCountConvention);
    IO.mapOptional("NoEscape", P.NoEscape);
    IO.mapOptional("Type", P.Type, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
typedef std::vector<NullabilityKind> NullabilitySeq;

struct AvailabilityItem {
  APIAvailability Mode = APIAvailability::Available;
  StringRef Msg;
};

/// Old attribute deprecated in favor of SwiftName.
enum class FactoryAsInitKind {
  /// Infer based on name and type (the default).
  Infer,
  /// Treat as a class method.
  AsClassMethod,
  /// Treat as an initializer.
  AsInitializer,
};

struct Method {
  StringRef Selector;
  MethodKind Kind;
  ParamsSeq Params;
  NullabilitySeq Nullability;
  Optional<NullabilityKind> NullabilityOfRet;
  Optional<RetainCountConventionKind> RetainCountConvention;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
  FactoryAsInitKind FactoryAsInit = FactoryAsInitKind::Infer;
  bool DesignatedInit = false;
  bool Required = false;
  StringRef ResultType;
};

typedef std::vector<Method> MethodsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Method)

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<FactoryAsInitKind> {
  static void enumeration(IO &IO, FactoryAsInitKind &FIK) {
    IO.enumCase(FIK, "A", FactoryAsInitKind::Infer);
    IO.enumCase(FIK, "C", FactoryAsInitKind::AsClassMethod);
    IO.enumCase(FIK, "I", FactoryAsInitKind::AsInitializer);
  }
};

template <> struct MappingTraits<Method> {
  static void mapping(IO &IO, Method &M) {
    IO.mapRequired("Selector", M.Selector);
    IO.mapRequired("MethodKind", M.Kind);
    IO.mapOptional("Parameters", M.Params);
    IO.mapOptional("Nullability", M.Nullability);
    IO.mapOptional("NullabilityOfRet", M.NullabilityOfRet, llvm::None);
    IO.mapOptional("RetainCountConvention", M.RetainCountConvention);
    IO.mapOptional("Availability", M.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", M.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", M.SwiftPrivate);
    IO.mapOptional("SwiftName", M.SwiftName, StringRef(""));
    IO.mapOptional("FactoryAsInit", M.FactoryAsInit, FactoryAsInitKind::Infer);
    IO.mapOptional("DesignatedInit", M.DesignatedInit, false);
    IO.mapOptional("Required", M.Required, false);
    IO.mapOptional("ResultType", M.ResultType, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Property {
  StringRef Name;
  llvm::Optional<MethodKind> Kind;
  llvm::Optional<NullabilityKind> Nullability;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
  Optional<bool> SwiftImportAsAccessors;
  StringRef Type;
};

typedef std::vector<Property> PropertiesSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Property)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<Property> {
  static void mapping(IO &IO, Property &P) {
    IO.mapRequired("Name", P.Name);
    IO.mapOptional("PropertyKind", P.Kind);
    IO.mapOptional("Nullability", P.Nullability, llvm::None);
    IO.mapOptional("Availability", P.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", P.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", P.SwiftPrivate);
    IO.mapOptional("SwiftName", P.SwiftName, StringRef(""));
    IO.mapOptional("SwiftImportAsAccessors", P.SwiftImportAsAccessors);
    IO.mapOptional("Type", P.Type, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Class {
  StringRef Name;
  bool AuditedForNullability = false;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
  Optional<StringRef> SwiftBridge;
  Optional<StringRef> NSErrorDomain;
  Optional<bool> SwiftImportAsNonGeneric;
  Optional<bool> SwiftObjCMembers;
  MethodsSeq Methods;
  PropertiesSeq Properties;
};

typedef std::vector<Class> ClassesSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Class)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<Class> {
  static void mapping(IO &IO, Class &C) {
    IO.mapRequired("Name", C.Name);
    IO.mapOptional("AuditedForNullability", C.AuditedForNullability, false);
    IO.mapOptional("Availability", C.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", C.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", C.SwiftPrivate);
    IO.mapOptional("SwiftName", C.SwiftName, StringRef(""));
    IO.mapOptional("SwiftBridge", C.SwiftBridge);
    IO.mapOptional("NSErrorDomain", C.NSErrorDomain);
    IO.mapOptional("SwiftImportAsNonGeneric", C.SwiftImportAsNonGeneric);
    IO.mapOptional("SwiftObjCMembers", C.SwiftObjCMembers);
    IO.mapOptional("Methods", C.Methods);
    IO.mapOptional("Properties", C.Properties);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Function {
  StringRef Name;
  ParamsSeq Params;
  NullabilitySeq Nullability;
  Optional<NullabilityKind> NullabilityOfRet;
  Optional<api_notes::RetainCountConventionKind> RetainCountConvention;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
  StringRef Type;
  StringRef ResultType;
};

typedef std::vector<Function> FunctionsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Function)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<Function> {
  static void mapping(IO &IO, Function &F) {
    IO.mapRequired("Name", F.Name);
    IO.mapOptional("Parameters", F.Params);
    IO.mapOptional("Nullability", F.Nullability);
    IO.mapOptional("NullabilityOfRet", F.NullabilityOfRet, llvm::None);
    IO.mapOptional("RetainCountConvention", F.RetainCountConvention);
    IO.mapOptional("Availability", F.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", F.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", F.SwiftPrivate);
    IO.mapOptional("SwiftName", F.SwiftName, StringRef(""));
    IO.mapOptional("ResultType", F.ResultType, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct GlobalVariable {
  StringRef Name;
  llvm::Optional<NullabilityKind> Nullability;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
  StringRef Type;
};

typedef std::vector<GlobalVariable> GlobalVariablesSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(GlobalVariable)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<GlobalVariable> {
  static void mapping(IO &IO, GlobalVariable &GV) {
    IO.mapRequired("Name", GV.Name);
    IO.mapOptional("Nullability", GV.Nullability, llvm::None);
    IO.mapOptional("Availability", GV.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", GV.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", GV.SwiftPrivate);
    IO.mapOptional("SwiftName", GV.SwiftName, StringRef(""));
    IO.mapOptional("Type", GV.Type, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct EnumConstant {
  StringRef Name;
  AvailabilityItem Availability;
  Optional<bool> SwiftPrivate;
  StringRef SwiftName;
};

typedef std::vector<EnumConstant> EnumConstantsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(EnumConstant)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<EnumConstant> {
  static void mapping(IO &IO, EnumConstant &EC) {
    IO.mapRequired("Name", EC.Name);
    IO.mapOptional("Availability", EC.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", EC.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", EC.SwiftPrivate);
    IO.mapOptional("SwiftName", EC.SwiftName, StringRef(""));
  }
};
} // namespace yaml
} // namespace llvm

namespace {
/// Syntactic sugar for EnumExtensibility and FlagEnum
enum class EnumConvenienceAliasKind {
  /// EnumExtensibility: none, FlagEnum: false
  None,
  /// EnumExtensibility: open, FlagEnum: false
  CFEnum,
  /// EnumExtensibility: open, FlagEnum: true
  CFOptions,
  /// EnumExtensibility: closed, FlagEnum: false
  CFClosedEnum
};
} // namespace

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<EnumConvenienceAliasKind> {
  static void enumeration(IO &IO, EnumConvenienceAliasKind &ECAK) {
    IO.enumCase(ECAK, "none", EnumConvenienceAliasKind::None);
    IO.enumCase(ECAK, "CFEnum", EnumConvenienceAliasKind::CFEnum);
    IO.enumCase(ECAK, "NSEnum", EnumConvenienceAliasKind::CFEnum);
    IO.enumCase(ECAK, "CFOptions", EnumConvenienceAliasKind::CFOptions);
    IO.enumCase(ECAK, "NSOptions", EnumConvenienceAliasKind::CFOptions);
    IO.enumCase(ECAK, "CFClosedEnum", EnumConvenienceAliasKind::CFClosedEnum);
    IO.enumCase(ECAK, "NSClosedEnum", EnumConvenienceAliasKind::CFClosedEnum);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Tag {
  StringRef Name;
  AvailabilityItem Availability;
  StringRef SwiftName;
  Optional<bool> SwiftPrivate;
  Optional<StringRef> SwiftBridge;
  Optional<StringRef> NSErrorDomain;
  Optional<EnumExtensibilityKind> EnumExtensibility;
  Optional<bool> FlagEnum;
  Optional<EnumConvenienceAliasKind> EnumConvenienceKind;
};

typedef std::vector<Tag> TagsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Tag)

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<EnumExtensibilityKind> {
  static void enumeration(IO &IO, EnumExtensibilityKind &EEK) {
    IO.enumCase(EEK, "none", EnumExtensibilityKind::None);
    IO.enumCase(EEK, "open", EnumExtensibilityKind::Open);
    IO.enumCase(EEK, "closed", EnumExtensibilityKind::Closed);
  }
};

template <> struct MappingTraits<Tag> {
  static void mapping(IO &IO, Tag &T) {
    IO.mapRequired("Name", T.Name);
    IO.mapOptional("Availability", T.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", T.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", T.SwiftPrivate);
    IO.mapOptional("SwiftName", T.SwiftName, StringRef(""));
    IO.mapOptional("SwiftBridge", T.SwiftBridge);
    IO.mapOptional("NSErrorDomain", T.NSErrorDomain);
    IO.mapOptional("EnumExtensibility", T.EnumExtensibility);
    IO.mapOptional("FlagEnum", T.FlagEnum);
    IO.mapOptional("EnumKind", T.EnumConvenienceKind);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Typedef {
  StringRef Name;
  AvailabilityItem Availability;
  StringRef SwiftName;
  Optional<bool> SwiftPrivate;
  Optional<StringRef> SwiftBridge;
  Optional<StringRef> NSErrorDomain;
  Optional<SwiftNewTypeKind> SwiftType;
};

typedef std::vector<Typedef> TypedefsSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Typedef)

namespace llvm {
namespace yaml {
template <> struct ScalarEnumerationTraits<SwiftNewTypeKind> {
  static void enumeration(IO &IO, SwiftNewTypeKind &SWK) {
    IO.enumCase(SWK, "none", SwiftNewTypeKind::None);
    IO.enumCase(SWK, "struct", SwiftNewTypeKind::Struct);
    IO.enumCase(SWK, "enum", SwiftNewTypeKind::Enum);
  }
};

template <> struct MappingTraits<Typedef> {
  static void mapping(IO &IO, Typedef &T) {
    IO.mapRequired("Name", T.Name);
    IO.mapOptional("Availability", T.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", T.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftPrivate", T.SwiftPrivate);
    IO.mapOptional("SwiftName", T.SwiftName, StringRef(""));
    IO.mapOptional("SwiftBridge", T.SwiftBridge);
    IO.mapOptional("NSErrorDomain", T.NSErrorDomain);
    IO.mapOptional("SwiftWrapper", T.SwiftType);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct TopLevelItems {
  ClassesSeq Classes;
  ClassesSeq Protocols;
  FunctionsSeq Functions;
  GlobalVariablesSeq Globals;
  EnumConstantsSeq EnumConstants;
  TagsSeq Tags;
  TypedefsSeq Typedefs;
};
} // namespace

namespace llvm {
namespace yaml {
static void mapTopLevelItems(IO &IO, TopLevelItems &TLI) {
  IO.mapOptional("Classes", TLI.Classes);
  IO.mapOptional("Protocols", TLI.Protocols);
  IO.mapOptional("Functions", TLI.Functions);
  IO.mapOptional("Globals", TLI.Globals);
  IO.mapOptional("Enumerators", TLI.EnumConstants);
  IO.mapOptional("Tags", TLI.Tags);
  IO.mapOptional("Typedefs", TLI.Typedefs);
}
} // namespace yaml
} // namespace llvm

namespace {
struct Versioned {
  VersionTuple Version;
  TopLevelItems Items;
};

typedef std::vector<Versioned> VersionedSeq;
} // namespace

LLVM_YAML_IS_SEQUENCE_VECTOR(Versioned)

namespace llvm {
namespace yaml {
template <> struct MappingTraits<Versioned> {
  static void mapping(IO &IO, Versioned &V) {
    IO.mapRequired("Version", V.Version);
    mapTopLevelItems(IO, V.Items);
  }
};
} // namespace yaml
} // namespace llvm

namespace {
struct Module {
  StringRef Name;
  AvailabilityItem Availability;
  TopLevelItems TopLevel;
  VersionedSeq SwiftVersions;

  llvm::Optional<bool> SwiftInferImportAsMember = {llvm::None};

#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
  LLVM_DUMP_METHOD void dump() /*const*/;
#endif
};
} // namespace

namespace llvm {
namespace yaml {
template <> struct MappingTraits<Module> {
  static void mapping(IO &IO, Module &M) {
    IO.mapRequired("Name", M.Name);
    IO.mapOptional("Availability", M.Availability.Mode,
                   APIAvailability::Available);
    IO.mapOptional("AvailabilityMsg", M.Availability.Msg, StringRef(""));
    IO.mapOptional("SwiftInferImportAsMember", M.SwiftInferImportAsMember);
    mapTopLevelItems(IO, M.TopLevel);
    IO.mapOptional("SwiftVersions", M.SwiftVersions);
  }
};
} // namespace yaml
} // namespace llvm

#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
LLVM_DUMP_METHOD void Module::dump() {
  llvm::yaml::Output OS(llvm::errs());
  OS << *this;
}
#endif

namespace {
bool parseAPINotes(StringRef YI, Module &M, llvm::SourceMgr::DiagHandlerTy Diag,
                   void *DiagContext) {
  llvm::yaml::Input IS(YI, nullptr, Diag, DiagContext);
  IS >> M;
  return static_cast<bool>(IS.error());
}
} // namespace

bool clang::api_notes::parseAndDumpAPINotes(StringRef YI,
                                            llvm::raw_ostream &OS) {
  Module M;
  if (parseAPINotes(YI, M, nullptr, nullptr))
    return true;

  llvm::yaml::Output YOS(OS);
  YOS << M;

  return false;
}
