[llvm] [llvm][Support] Add YAMLSchemeGen for producing YAML Schemes from YAMLTraits (PR #133284)

via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 14 04:43:54 PDT 2025


https://github.com/tgs-sc updated https://github.com/llvm/llvm-project/pull/133284

>From 4d11015bc2fcc02d6ce0ed5dbc4665ba3d414e97 Mon Sep 17 00:00:00 2001
From: Timur Golubovich <timur.golubovich at syntacore.com>
Date: Thu, 27 Mar 2025 20:04:08 +0300
Subject: [PATCH] [llvm][Support] Add YAMLGenerateSchema for producing YAML
 schemas

Introduced a way for generating schema for validating input YAML. This can be
useful to see the full structure of the input YAML file for different llvm
based tools that use existing YAML parser, for example clang-format,
clang-tidy e.t.c. This commit also can be useful for yaml-language-server.
---
 .../include/llvm/Support/YAMLGenerateSchema.h | 504 ++++++++++++++++++
 llvm/include/llvm/Support/YAMLTraits.h        |  87 +--
 llvm/lib/Support/CMakeLists.txt               |   1 +
 llvm/lib/Support/YAMLGenerateSchema.cpp       | 168 ++++++
 llvm/lib/Support/YAMLTraits.cpp               |   4 +
 llvm/unittests/Support/CMakeLists.txt         |   1 +
 .../Support/YAMLGenerateSchemaTest.cpp        | 103 ++++
 .../gn/secondary/llvm/lib/Support/BUILD.gn    |   1 +
 8 files changed, 834 insertions(+), 35 deletions(-)
 create mode 100644 llvm/include/llvm/Support/YAMLGenerateSchema.h
 create mode 100644 llvm/lib/Support/YAMLGenerateSchema.cpp
 create mode 100644 llvm/unittests/Support/YAMLGenerateSchemaTest.cpp

diff --git a/llvm/include/llvm/Support/YAMLGenerateSchema.h b/llvm/include/llvm/Support/YAMLGenerateSchema.h
new file mode 100644
index 0000000000000..6344b18e5c8f0
--- /dev/null
+++ b/llvm/include/llvm/Support/YAMLGenerateSchema.h
@@ -0,0 +1,504 @@
+//===- llvm/Support/YAMLGenerateSchema.h ------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_YAMLGENERATE_SCHEMA_H
+#define LLVM_SUPPORT_YAMLGENERATE_SCHEMA_H
+
+#include "llvm/Support/YAMLTraits.h"
+
+namespace llvm {
+
+namespace yaml {
+
+class GenerateSchema : public IO {
+public:
+  GenerateSchema(raw_ostream &RO, void *Ctxt = nullptr, int WrapColumn = 70);
+  ~GenerateSchema() override = default;
+
+  IOKind getKind() const override;
+  bool outputting() const override;
+  bool mapTag(StringRef, bool) override;
+  void beginMapping() override;
+  void endMapping() override;
+  bool preflightKey(const char *key, bool, bool, bool &, void *&) override;
+  void postflightKey(void *) override;
+  std::vector<StringRef> keys() override;
+  void beginFlowMapping() override;
+  void endFlowMapping() override;
+  unsigned beginSequence() override;
+  void endSequence() override;
+  bool preflightElement(unsigned, void *&) override;
+  void postflightElement(void *) override;
+  unsigned beginFlowSequence() override;
+  bool preflightFlowElement(unsigned, void *&) override;
+  void postflightFlowElement(void *) override;
+  void endFlowSequence() override;
+  void beginEnumScalar() override;
+  bool matchEnumScalar(const char *, bool) override;
+  bool matchEnumFallback() override;
+  void endEnumScalar() override;
+  bool beginBitSetScalar(bool &) override;
+  bool bitSetMatch(const char *, bool) override;
+  void endBitSetScalar() override;
+  void scalarString(StringRef &, QuotingType) override;
+  void blockScalarString(StringRef &) override;
+  void scalarTag(std::string &) override;
+  NodeKind getNodeKind() override;
+  void setError(const Twine &message) override;
+  std::error_code error() override;
+  bool canElideEmptySequence() override;
+
+  // These are only used by operator<<. They could be private
+  // if that templated operator could be made a friend.
+  void beginDocuments();
+  bool preflightDocument(unsigned);
+  void postflightDocument();
+  void endDocuments();
+
+  // These are needed to yamlize schema.
+  struct YNode {
+  public:
+    YNode(NodeKind Kind) : Kind(Kind) {}
+
+    NodeKind getNodeKind() const { return Kind; }
+
+  private:
+    NodeKind Kind;
+  };
+
+  class ScalarYNode final : public YNode {
+    StringRef Str;
+    QuotingType QT;
+
+  public:
+    ScalarYNode(StringRef Str, QuotingType QT)
+        : YNode(NodeKind::Scalar), Str(Str), QT(QT) {}
+
+    StringRef getValue() const { return Str; }
+
+    QuotingType getQuotingType() const { return QT; }
+  };
+
+  class MapYNode final : public YNode,
+                         SmallVector<std::pair<StringRef, YNode *>, 8> {
+  public:
+    using BaseVector = SmallVector<std::pair<StringRef, YNode *>, 8>;
+
+    MapYNode() : YNode(NodeKind::Map) {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+  };
+
+  class SequenceYNode final : public YNode, SmallVector<YNode *, 8> {
+  public:
+    using BaseVector = SmallVector<YNode *, 8>;
+
+    SequenceYNode() : YNode(NodeKind::Sequence) {}
+
+    using BaseVector::operator[];
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::resize;
+    using BaseVector::size;
+  };
+
+private:
+  template <typename YNodeType, typename... YNodeArgs>
+  YNodeType *createYNode(YNodeArgs &&...Args) {
+    auto UPtr = std::make_unique<YNodeType>(std::forward<YNodeArgs>(Args)...);
+    auto *Ptr = UPtr.get();
+    YNodes.emplace_back(std::move(UPtr));
+    return Ptr;
+  }
+
+public:
+  ScalarYNode *createScalarYNode(StringRef Str) {
+    return createYNode<ScalarYNode>(Str, QuotingType::None);
+  }
+
+  MapYNode *createMapYNode() { return createYNode<MapYNode>(); }
+
+  SequenceYNode *createSequenceYNode() { return createYNode<SequenceYNode>(); }
+
+  std::vector<std::unique_ptr<YNode>> YNodes;
+
+  using StringVector = SmallVector<StringRef, 8>;
+
+  class SchemaNode {
+  public:
+    virtual YNode *yamlize(GenerateSchema &Gen) const = 0;
+
+    virtual ~SchemaNode() = default;
+  };
+
+  enum class PropertyKind : uint8_t {
+    Properties,
+    Required,
+    Type,
+    Enum,
+    Items,
+  };
+
+  class Schema;
+
+  class SchemaProperty : public SchemaNode {
+    PropertyKind Kind;
+    StringRef Name;
+
+  public:
+    SchemaProperty(PropertyKind Kind, StringRef Name)
+        : Kind(Kind), Name(Name) {}
+
+    PropertyKind getKind() const { return Kind; }
+
+    StringRef getName() const { return Name; }
+  };
+
+  class PropertiesProperty final
+      : public SchemaProperty,
+        SmallVector<std::pair<StringRef, Schema *>, 8> {
+  public:
+    using BaseVector = SmallVector<std::pair<StringRef, Schema *>, 8>;
+
+    PropertiesProperty()
+        : SchemaProperty(PropertyKind::Properties, "properties") {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      auto *Map = Gen.createMapYNode();
+      for (auto &&[Key, Value] : *this) {
+        auto *Y = Value->yamlize(Gen);
+        Map->emplace_back(Key, Y);
+      }
+      return Map;
+    }
+  };
+
+  class RequiredProperty final : public SchemaProperty, StringVector {
+  public:
+    using BaseVector = StringVector;
+
+    RequiredProperty() : SchemaProperty(PropertyKind::Required, "required") {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      if (size() == 1) {
+        auto *Scalar = Gen.createScalarYNode(*begin());
+        return Scalar;
+      }
+      auto *Sequence = Gen.createSequenceYNode();
+      for (auto &&Value : *this) {
+        auto *Scalar = Gen.createScalarYNode(Value);
+        Sequence->emplace_back(Scalar);
+      }
+      return Sequence;
+    }
+  };
+
+  class TypeProperty final : public SchemaProperty {
+    StringRef Value = "any";
+
+  public:
+    TypeProperty() : SchemaProperty(PropertyKind::Type, "type") {}
+
+    StringRef getValue() const { return Value; }
+
+    void setValue(StringRef Val) { Value = Val; }
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      auto *Scalar = Gen.createScalarYNode(Value);
+      return Scalar;
+    }
+  };
+
+  class EnumProperty final : public SchemaProperty, StringVector {
+  public:
+    using BaseVector = StringVector;
+
+    EnumProperty() : SchemaProperty(PropertyKind::Enum, "enum") {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      auto *Sequence = Gen.createSequenceYNode();
+      for (auto &&Value : *this) {
+        auto *Scalar = Gen.createScalarYNode(Value);
+        Sequence->emplace_back(Scalar);
+      }
+      return Sequence;
+    }
+  };
+
+  class ItemsProperty final : public SchemaProperty, SmallVector<Schema *, 8> {
+  public:
+    using BaseVector = SmallVector<Schema *, 8>;
+
+    ItemsProperty() : SchemaProperty(PropertyKind::Items, "items") {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      auto *Sequence = Gen.createSequenceYNode();
+      for (auto &&Value : *this) {
+        auto *Y = Value->yamlize(Gen);
+        Sequence->emplace_back(Y);
+      }
+      return Sequence;
+    }
+  };
+
+  class Schema final : public SchemaNode, SmallVector<SchemaProperty *, 8> {
+  public:
+    using BaseVector = SmallVector<SchemaProperty *, 8>;
+
+    Schema() = default;
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    template <typename PropertyType> PropertyType *findProperty() const {
+      auto P = PropertyType{};
+      auto Found = std::find_if(begin(), end(), [&](auto &&Prop) {
+        return Prop->getKind() == P.getKind();
+      });
+      return Found != end() ? static_cast<PropertyType *>(*Found) : nullptr;
+    }
+
+    template <typename PropertyType> bool hasProperty() const {
+      return findProperty<PropertyType>() != nullptr;
+    }
+
+    YNode *yamlize(GenerateSchema &Gen) const override {
+      auto *Map = Gen.createMapYNode();
+      for (auto &&Value : *this) {
+        auto *Y = Value->yamlize(Gen);
+        auto Name = Value->getName();
+        Map->emplace_back(Name, Y);
+      }
+      return Map;
+    }
+  };
+
+private:
+  std::vector<std::unique_ptr<SchemaNode>> SchemaNodes;
+  SmallVector<Schema *, 8> Schemas;
+
+  template <typename PropertyType> PropertyType *createProperty() {
+    auto UPtr = std::make_unique<PropertyType>();
+    auto *Ptr = UPtr.get();
+    SchemaNodes.emplace_back(std::move(UPtr));
+    return Ptr;
+  }
+
+public:
+  template <typename PropertyType>
+  PropertyType *getOrCreateProperty(Schema &S) {
+    auto Found = S.findProperty<PropertyType>();
+    if (!Found) {
+      Found = createProperty<PropertyType>();
+      S.emplace_back(Found);
+    }
+    return Found;
+  }
+
+  Schema *createSchema() {
+    auto UPtr = std::make_unique<Schema>();
+    auto *Ptr = UPtr.get();
+    SchemaNodes.emplace_back(std::move(UPtr));
+    return Ptr;
+  }
+
+  Schema *getTopSchema() const {
+    return Schemas.empty() ? nullptr : Schemas.back();
+  }
+
+private:
+  Output O;
+  SchemaNode *Root = nullptr;
+};
+
+template <> struct PolymorphicTraits<GenerateSchema::YNode> {
+  static NodeKind getKind(const GenerateSchema::YNode &N) {
+    return N.getNodeKind();
+  }
+
+  static GenerateSchema::ScalarYNode &getAsScalar(GenerateSchema::YNode &N) {
+    return (GenerateSchema::ScalarYNode &)N;
+  }
+
+  static GenerateSchema::MapYNode &getAsMap(GenerateSchema::YNode &N) {
+    return (GenerateSchema::MapYNode &)N;
+  }
+
+  static GenerateSchema::SequenceYNode &
+  getAsSequence(GenerateSchema::YNode &N) {
+    return (GenerateSchema::SequenceYNode &)N;
+  }
+};
+
+template <> struct ScalarTraits<GenerateSchema::ScalarYNode> {
+  static void output(const GenerateSchema::ScalarYNode &N, void *Ctx,
+                     llvm::raw_ostream &Out) {
+    Out << N.getValue();
+  }
+
+  static StringRef input(StringRef Scalar, void *Ctx,
+                         GenerateSchema::ScalarYNode &N) {
+    return {};
+  }
+
+  static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+};
+
+template <> struct MappingTraits<GenerateSchema::MapYNode> {
+  static void mapping(IO &io, GenerateSchema::MapYNode &N) {
+    for (auto &&[Key, Value] : N) {
+      io.mapRequired(Key.data(), *Value);
+    }
+  }
+};
+
+template <> struct SequenceTraits<GenerateSchema::SequenceYNode> {
+  static size_t size(IO &io, GenerateSchema::SequenceYNode &N) {
+    return N.size();
+  }
+
+  static GenerateSchema::YNode &element(IO &, GenerateSchema::SequenceYNode &N,
+                                        size_t Idx) {
+    if (Idx >= N.size())
+      N.resize(Idx + 1);
+    return *(N[Idx]);
+  }
+};
+
+// Define non-member operator<< so that Output can stream out document list.
+template <typename T>
+inline std::enable_if_t<has_DocumentListTraits<T>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &DocList) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  const size_t count = DocumentListTraits<T>::size(Gen, DocList);
+  for (size_t i = 0; i < count; ++i) {
+    if (Gen.preflightDocument(i)) {
+      yamlize(Gen, DocumentListTraits<T>::element(Gen, DocList, i), true, Ctx);
+      Gen.postflightDocument();
+    }
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Define non-member operator<< so that Output can stream out a map.
+template <typename T>
+inline std::enable_if_t<has_MappingTraits<T, EmptyContext>::value,
+                        GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &Map) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  if (Gen.preflightDocument(0)) {
+    yamlize(Gen, Map, true, Ctx);
+    Gen.postflightDocument();
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Define non-member operator<< so that Output can stream out a sequence.
+template <typename T>
+inline std::enable_if_t<has_SequenceTraits<T>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &Seq) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  if (Gen.preflightDocument(0)) {
+    yamlize(Gen, Seq, true, Ctx);
+    Gen.postflightDocument();
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Define non-member operator<< so that Output can stream out a block scalar.
+template <typename T>
+inline std::enable_if_t<has_BlockScalarTraits<T>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &Val) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  if (Gen.preflightDocument(0)) {
+    yamlize(Gen, Val, true, Ctx);
+    Gen.postflightDocument();
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Define non-member operator<< so that Output can stream out a string map.
+template <typename T>
+inline std::enable_if_t<has_CustomMappingTraits<T>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &Val) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  if (Gen.preflightDocument(0)) {
+    yamlize(Gen, Val, true, Ctx);
+    Gen.postflightDocument();
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Define non-member operator<< so that Output can stream out a polymorphic
+// type.
+template <typename T>
+inline std::enable_if_t<has_PolymorphicTraits<T>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &Val) {
+  EmptyContext Ctx;
+  Gen.beginDocuments();
+  if (Gen.preflightDocument(0)) {
+    // FIXME: The parser does not support explicit documents terminated with a
+    // plain scalar; the end-marker is included as part of the scalar token.
+    assert(PolymorphicTraits<T>::getKind(Val) != NodeKind::Scalar &&
+           "plain scalar documents are not supported");
+    yamlize(Gen, Val, true, Ctx);
+    Gen.postflightDocument();
+  }
+  Gen.endDocuments();
+  return Gen;
+}
+
+// Provide better error message about types missing a trait specialization
+template <typename T>
+inline std::enable_if_t<missingTraits<T, EmptyContext>::value, GenerateSchema &>
+operator<<(GenerateSchema &Gen, T &seq) {
+  char missing_yaml_trait_for_type[sizeof(MissingTrait<T>)];
+  return Gen;
+}
+
+} // namespace yaml
+
+} // namespace llvm
+
+#endif // LLVM_SUPPORT_YAMLGENERATE_SCHEMA_H
diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h
index e707a445012b5..029484705be2a 100644
--- a/llvm/include/llvm/Support/YAMLTraits.h
+++ b/llvm/include/llvm/Support/YAMLTraits.h
@@ -774,12 +774,19 @@ struct unvalidatedMappingTraits
           bool, has_MappingTraits<T, Context>::value &&
                     !has_MappingValidateTraits<T, Context>::value> {};
 
+enum class IOKind : uint8_t {
+  Outputting,
+  Inputting,
+  GeneratingSchema,
+};
+
 // Base class for Input and Output.
 class IO {
 public:
   IO(void *Ctxt = nullptr);
   virtual ~IO();
 
+  virtual IOKind getKind() const = 0;
   virtual bool outputting() const = 0;
 
   virtual unsigned beginSequence() = 0;
@@ -824,15 +831,17 @@ class IO {
 
   template <typename T>
   void enumCase(T &Val, const char* Str, const T ConstVal) {
-    if ( matchEnumScalar(Str, outputting() && Val == ConstVal) ) {
+    if (matchEnumScalar(Str,
+                        getKind() == IOKind::Outputting && Val == ConstVal)) {
       Val = ConstVal;
     }
   }
 
   // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF
   template <typename T>
-  void enumCase(T &Val, const char* Str, const uint32_t ConstVal) {
-    if ( matchEnumScalar(Str, outputting() && Val == static_cast<T>(ConstVal)) ) {
+  void enumCase(T &Val, const char *Str, const uint32_t ConstVal) {
+    if (matchEnumScalar(Str, getKind() == IOKind::Outputting &&
+                                 Val == static_cast<T>(ConstVal))) {
       Val = ConstVal;
     }
   }
@@ -849,8 +858,9 @@ class IO {
   }
 
   template <typename T>
-  void bitSetCase(T &Val, const char* Str, const T ConstVal) {
-    if ( bitSetMatch(Str, outputting() && (Val & ConstVal) == ConstVal) ) {
+  void bitSetCase(T &Val, const char *Str, const T ConstVal) {
+    if (bitSetMatch(Str, getKind() == IOKind::Outputting &&
+                             (Val & ConstVal) == ConstVal)) {
       Val = static_cast<T>(Val | ConstVal);
     }
   }
@@ -858,21 +868,24 @@ class IO {
   // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF
   template <typename T>
   void bitSetCase(T &Val, const char* Str, const uint32_t ConstVal) {
-    if ( bitSetMatch(Str, outputting() && (Val & ConstVal) == ConstVal) ) {
+    if (bitSetMatch(Str, getKind() == IOKind::Outputting &&
+                             (Val & ConstVal) == ConstVal)) {
       Val = static_cast<T>(Val | ConstVal);
     }
   }
 
   template <typename T>
   void maskedBitSetCase(T &Val, const char *Str, T ConstVal, T Mask) {
-    if (bitSetMatch(Str, outputting() && (Val & Mask) == ConstVal))
+    if (bitSetMatch(Str, getKind() == IOKind::Outputting &&
+                             (Val & Mask) == ConstVal))
       Val = Val | ConstVal;
   }
 
   template <typename T>
   void maskedBitSetCase(T &Val, const char *Str, uint32_t ConstVal,
                         uint32_t Mask) {
-    if (bitSetMatch(Str, outputting() && (Val & Mask) == ConstVal))
+    if (bitSetMatch(Str, getKind() == IOKind::Outputting &&
+                             (Val & Mask) == ConstVal))
       Val = Val | ConstVal;
   }
 
@@ -942,7 +955,8 @@ class IO {
                              bool Required, Context &Ctx) {
     void *SaveInfo;
     bool UseDefault;
-    const bool sameAsDefault = outputting() && Val == DefaultValue;
+    const bool sameAsDefault =
+        (getKind() == IOKind::Outputting) && Val == DefaultValue;
     if ( this->preflightKey(Key, Required, sameAsDefault, UseDefault,
                                                                   SaveInfo) ) {
       yamlize(*this, Val, Required, Ctx);
@@ -1004,14 +1018,13 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
 template <typename T>
 std::enable_if_t<has_ScalarTraits<T>::value, void> yamlize(IO &io, T &Val, bool,
                                                            EmptyContext &Ctx) {
-  if ( io.outputting() ) {
+  if (io.getKind() != IOKind::Inputting) {
     SmallString<128> Storage;
     raw_svector_ostream Buffer(Storage);
     ScalarTraits<T>::output(Val, io.getContext(), Buffer);
     StringRef Str = Buffer.str();
     io.scalarString(Str, ScalarTraits<T>::mustQuote(Str));
-  }
-  else {
+  } else {
     StringRef Str;
     io.scalarString(Str, ScalarTraits<T>::mustQuote(Str));
     StringRef Result = ScalarTraits<T>::input(Str, io.getContext(), Val);
@@ -1024,7 +1037,7 @@ std::enable_if_t<has_ScalarTraits<T>::value, void> yamlize(IO &io, T &Val, bool,
 template <typename T>
 std::enable_if_t<has_BlockScalarTraits<T>::value, void>
 yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) {
-  if (YamlIO.outputting()) {
+  if (YamlIO.getKind() != IOKind::Inputting) {
     std::string Storage;
     raw_string_ostream Buffer(Storage);
     BlockScalarTraits<T>::output(Val, YamlIO.getContext(), Buffer);
@@ -1043,7 +1056,7 @@ yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) {
 template <typename T>
 std::enable_if_t<has_TaggedScalarTraits<T>::value, void>
 yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
-  if (io.outputting()) {
+  if (io.getKind() != IOKind::Inputting) {
     std::string ScalarStorage, TagStorage;
     raw_string_ostream ScalarBuffer(ScalarStorage), TagBuffer(TagStorage);
     TaggedScalarTraits<T>::output(Val, io.getContext(), ScalarBuffer,
@@ -1085,7 +1098,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) {
     io.beginFlowMapping();
   else
     io.beginMapping();
-  if (io.outputting()) {
+  if (io.getKind() == IOKind::Outputting) {
     std::string Err = detail::doValidate(io, Val, Ctx);
     if (!Err.empty()) {
       errs() << Err << "\n";
@@ -1093,7 +1106,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) {
     }
   }
   detail::doMapping(io, Val, Ctx);
-  if (!io.outputting()) {
+  if (io.getKind() == IOKind::Inputting) {
     std::string Err = detail::doValidate(io, Val, Ctx);
     if (!Err.empty())
       io.setError(Err);
@@ -1113,7 +1126,7 @@ yamlizeMappingEnumInput(IO &io, T &Val) {
 template <typename T, typename Context>
 std::enable_if_t<has_MappingEnumInputTraits<T, Context>::value, bool>
 yamlizeMappingEnumInput(IO &io, T &Val) {
-  if (io.outputting())
+  if (io.getKind() != IOKind::Inputting)
     return false;
 
   io.beginEnumScalar();
@@ -1142,7 +1155,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) {
 template <typename T>
 std::enable_if_t<has_CustomMappingTraits<T>::value, void>
 yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
-  if ( io.outputting() ) {
+  if (io.getKind() != IOKind::Inputting) {
     io.beginMapping();
     CustomMappingTraits<T>::output(io, Val);
     io.endMapping();
@@ -1157,8 +1170,9 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
 template <typename T>
 std::enable_if_t<has_PolymorphicTraits<T>::value, void>
 yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
-  switch (io.outputting() ? PolymorphicTraits<T>::getKind(Val)
-                          : io.getNodeKind()) {
+  switch ((io.getKind() == IOKind::Inputting)
+              ? io.getNodeKind()
+              : PolymorphicTraits<T>::getKind(Val)) {
   case NodeKind::Scalar:
     return yamlize(io, PolymorphicTraits<T>::getAsScalar(Val), true, Ctx);
   case NodeKind::Map:
@@ -1179,7 +1193,9 @@ std::enable_if_t<has_SequenceTraits<T>::value, void>
 yamlize(IO &io, T &Seq, bool, Context &Ctx) {
   if ( has_FlowTraits< SequenceTraits<T>>::value ) {
     unsigned incnt = io.beginFlowSequence();
-    unsigned count = io.outputting() ? SequenceTraits<T>::size(io, Seq) : incnt;
+    unsigned count = (io.getKind() == IOKind::Outputting)
+                         ? SequenceTraits<T>::size(io, Seq)
+                         : incnt;
     for(unsigned i=0; i < count; ++i) {
       void *SaveInfo;
       if ( io.preflightFlowElement(i, SaveInfo) ) {
@@ -1191,7 +1207,9 @@ yamlize(IO &io, T &Seq, bool, Context &Ctx) {
   }
   else {
     unsigned incnt = io.beginSequence();
-    unsigned count = io.outputting() ? SequenceTraits<T>::size(io, Seq) : incnt;
+    unsigned count = (io.getKind() == IOKind::Outputting)
+                         ? SequenceTraits<T>::size(io, Seq)
+                         : incnt;
     for(unsigned i=0; i < count; ++i) {
       void *SaveInfo;
       if ( io.preflightElement(i, SaveInfo) ) {
@@ -1358,16 +1376,15 @@ template <typename TNorm, typename TFinal>
 struct MappingNormalization {
   MappingNormalization(IO &i_o, TFinal &Obj)
       : io(i_o), BufPtr(nullptr), Result(Obj) {
-    if ( io.outputting() ) {
+    if (io.getKind() == IOKind::Outputting) {
       BufPtr = new (&Buffer) TNorm(io, Obj);
-    }
-    else {
+    } else {
       BufPtr = new (&Buffer) TNorm(io);
     }
   }
 
   ~MappingNormalization() {
-    if ( ! io.outputting() ) {
+    if (io.getKind() == IOKind::Inputting) {
       Result = BufPtr->denormalize(io);
     }
     BufPtr->~TNorm();
@@ -1390,10 +1407,9 @@ template <typename TNorm, typename TFinal>
 struct MappingNormalizationHeap {
   MappingNormalizationHeap(IO &i_o, TFinal &Obj, BumpPtrAllocator *allocator)
     : io(i_o), Result(Obj) {
-    if ( io.outputting() ) {
+    if (io.getKind() == IOKind::Outputting) {
       BufPtr = new (&Buffer) TNorm(io, Obj);
-    }
-    else if (allocator) {
+    } else if (allocator) {
       BufPtr = allocator->Allocate<TNorm>();
       new (BufPtr) TNorm(io);
     } else {
@@ -1402,10 +1418,9 @@ struct MappingNormalizationHeap {
   }
 
   ~MappingNormalizationHeap() {
-    if ( io.outputting() ) {
+    if (io.getKind() == IOKind::Outputting) {
       BufPtr->~TNorm();
-    }
-    else {
+    } else {
       Result = BufPtr->denormalize(io);
     }
   }
@@ -1452,6 +1467,7 @@ class Input : public IO {
   std::error_code error() override;
 
 private:
+  IOKind getKind() const override;
   bool outputting() const override;
   bool mapTag(StringRef, bool) override;
   void beginMapping() override;
@@ -1603,6 +1619,7 @@ class Output : public IO {
   /// anyway.
   void setWriteDefaultValues(bool Write) { WriteDefaultValues = Write; }
 
+  IOKind getKind() const override;
   bool outputting() const override;
   bool mapTag(StringRef, bool) override;
   void beginMapping() override;
@@ -1688,8 +1705,8 @@ void IO::processKeyWithDefault(const char *Key, std::optional<T> &Val,
   assert(!DefaultValue && "std::optional<T> shouldn't have a value!");
   void *SaveInfo;
   bool UseDefault = true;
-  const bool sameAsDefault = outputting() && !Val;
-  if (!outputting() && !Val)
+  const bool sameAsDefault = (getKind() == IOKind::Outputting) && !Val;
+  if (!(getKind() == IOKind::Outputting) && !Val)
     Val = T();
   if (Val &&
       this->preflightKey(Key, Required, sameAsDefault, UseDefault, SaveInfo)) {
@@ -1699,7 +1716,7 @@ void IO::processKeyWithDefault(const char *Key, std::optional<T> &Val,
     // was requested, i.e. the DefaultValue will be assigned. The DefaultValue
     // is usually None.
     bool IsNone = false;
-    if (!outputting())
+    if (getKind() == IOKind::Inputting)
       if (const auto *Node =
               dyn_cast<ScalarNode>(((Input *)this)->getCurrentNode()))
         // We use rtrim to ignore possible white spaces that might exist when a
diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index 2754c97fce6c1..580ea5ef51079 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -272,6 +272,7 @@ add_llvm_component_library(LLVMSupport
   WithColor.cpp
   YAMLParser.cpp
   YAMLTraits.cpp
+  YAMLGenerateSchema.cpp
   raw_os_ostream.cpp
   raw_ostream.cpp
   raw_socket_stream.cpp
diff --git a/llvm/lib/Support/YAMLGenerateSchema.cpp b/llvm/lib/Support/YAMLGenerateSchema.cpp
new file mode 100644
index 0000000000000..c2968cf03dec0
--- /dev/null
+++ b/llvm/lib/Support/YAMLGenerateSchema.cpp
@@ -0,0 +1,168 @@
+//===- lib/Support/YAMLGenerateSchema.cpp ---------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/YAMLGenerateSchema.h"
+
+using namespace llvm;
+using namespace yaml;
+
+//===----------------------------------------------------------------------===//
+//  GenerateSchema
+//===----------------------------------------------------------------------===//
+
+GenerateSchema::GenerateSchema(raw_ostream &RO, void *Ctxt, int WrapColumn)
+    : O(RO, Ctxt, WrapColumn) {}
+
+IOKind GenerateSchema::getKind() const { return IOKind::GeneratingSchema; }
+
+bool GenerateSchema::outputting() const { return false; }
+
+bool GenerateSchema::mapTag(StringRef, bool) { return false; }
+
+void GenerateSchema::beginMapping() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = getOrCreateProperty<TypeProperty>(*Top);
+  Type->setValue("object");
+}
+
+void GenerateSchema::endMapping() {}
+
+bool GenerateSchema::preflightKey(const char *Key, bool Required,
+                                  bool SameAsDefault, bool &UseDefault,
+                                  void *&SaveInfo) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  if (Required) {
+    auto *Req = getOrCreateProperty<RequiredProperty>(*Top);
+    Req->emplace_back(Key);
+  }
+  auto *S = createSchema();
+  auto *Properties = getOrCreateProperty<PropertiesProperty>(*Top);
+  Properties->emplace_back(Key, S);
+  Schemas.push_back(S);
+  return true;
+}
+
+void GenerateSchema::postflightKey(void *) {
+  assert(!Schemas.empty());
+  Schemas.pop_back();
+}
+
+std::vector<StringRef> GenerateSchema::keys() { return {}; }
+
+void GenerateSchema::beginFlowMapping() { beginMapping(); }
+
+void GenerateSchema::endFlowMapping() { endMapping(); }
+
+unsigned GenerateSchema::beginSequence() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = getOrCreateProperty<TypeProperty>(*Top);
+  Type->setValue("array");
+  getOrCreateProperty<ItemsProperty>(*Top);
+  return 1;
+}
+
+void GenerateSchema::endSequence() {}
+
+bool GenerateSchema::preflightElement(unsigned, void *&) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *S = createSchema();
+  auto *Items = getOrCreateProperty<ItemsProperty>(*Top);
+  Items->emplace_back(S);
+  Schemas.push_back(S);
+  return true;
+}
+
+void GenerateSchema::postflightElement(void *) {
+  assert(!Schemas.empty());
+  Schemas.pop_back();
+}
+
+unsigned GenerateSchema::beginFlowSequence() { return beginSequence(); }
+
+bool GenerateSchema::preflightFlowElement(unsigned Arg1, void *&Arg2) {
+  return preflightElement(Arg1, Arg2);
+}
+
+void GenerateSchema::postflightFlowElement(void *Arg1) {
+  postflightElement(Arg1);
+}
+
+void GenerateSchema::endFlowSequence() { endSequence(); }
+
+void GenerateSchema::beginEnumScalar() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = getOrCreateProperty<TypeProperty>(*Top);
+  Type->setValue("string");
+  getOrCreateProperty<EnumProperty>(*Top);
+}
+
+bool GenerateSchema::matchEnumScalar(const char *Val, bool) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Enum = getOrCreateProperty<EnumProperty>(*Top);
+  Enum->emplace_back(Val);
+  return false;
+}
+
+bool GenerateSchema::matchEnumFallback() { return false; }
+
+void GenerateSchema::endEnumScalar() {}
+
+bool GenerateSchema::beginBitSetScalar(bool &) {
+  beginEnumScalar();
+  return true;
+}
+
+bool GenerateSchema::bitSetMatch(const char *Val, bool Arg) {
+  return matchEnumScalar(Val, Arg);
+}
+
+void GenerateSchema::endBitSetScalar() { endEnumScalar(); }
+
+void GenerateSchema::scalarString(StringRef &Val, QuotingType) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = getOrCreateProperty<TypeProperty>(*Top);
+  Type->setValue("string");
+}
+
+void GenerateSchema::blockScalarString(StringRef &Val) {}
+
+void GenerateSchema::scalarTag(std::string &val) {}
+
+NodeKind GenerateSchema::getNodeKind() { report_fatal_error("invalid call"); }
+
+void GenerateSchema::setError(const Twine &) {}
+
+std::error_code GenerateSchema::error() { return {}; }
+
+bool GenerateSchema::canElideEmptySequence() { return false; }
+
+// These are only used by operator<<. They could be private
+// if that templated operator could be made a friend.
+void GenerateSchema::beginDocuments() {}
+
+bool GenerateSchema::preflightDocument(unsigned) {
+  auto *S = createSchema();
+  Root = S;
+  Schemas.push_back(S);
+  return true;
+}
+
+void GenerateSchema::postflightDocument() {
+  assert(!Schemas.empty());
+  Schemas.pop_back();
+  O << *Root->yamlize(*this);
+}
+
+void GenerateSchema::endDocuments() {}
diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp
index 035828b594e84..393b6ac0a2ee7 100644
--- a/llvm/lib/Support/YAMLTraits.cpp
+++ b/llvm/lib/Support/YAMLTraits.cpp
@@ -74,6 +74,8 @@ Input::~Input() = default;
 
 std::error_code Input::error() { return EC; }
 
+IOKind Input::getKind() const { return IOKind::Inputting; }
+
 bool Input::outputting() const {
   return false;
 }
@@ -485,6 +487,8 @@ Output::Output(raw_ostream &yout, void *context, int WrapColumn)
 
 Output::~Output() = default;
 
+IOKind Output::getKind() const { return IOKind::Outputting; }
+
 bool Output::outputting() const {
   return true;
 }
diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index 6c4e7cb689b20..f9ae91c544d0f 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -101,6 +101,7 @@ add_llvm_unittest(SupportTests
   VirtualFileSystemTest.cpp
   WithColorTest.cpp
   YAMLIOTest.cpp
+  YAMLGenerateSchemaTest.cpp
   YAMLParserTest.cpp
   buffer_ostream_test.cpp
   formatted_raw_ostream_test.cpp
diff --git a/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
new file mode 100644
index 0000000000000..6e1999271e5a2
--- /dev/null
+++ b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
@@ -0,0 +1,103 @@
+//===- YAMLGenerateSchemaTest.cpp - Tests for Generating YAML Schema ------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/YAMLGenerateSchema.h"
+#include "llvm/Support/YAMLTraits.h"
+#include <string>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+enum class ColorTy {
+  White,
+  Black,
+  Blue,
+};
+
+struct Baby {
+  std::string Name;
+  ColorTy Color;
+};
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(Baby)
+
+struct Animal {
+  std::string Name;
+  std::optional<int> Age;
+  std::vector<Baby> Babies;
+};
+
+LLVM_YAML_IS_SEQUENCE_VECTOR(Animal)
+
+namespace llvm {
+namespace yaml {
+
+template <> struct ScalarEnumerationTraits<ColorTy> {
+  static void enumeration(IO &io, ColorTy &value) {
+    io.enumCase(value, "white", ColorTy::White);
+    io.enumCase(value, "black", ColorTy::Black);
+    io.enumCase(value, "blue", ColorTy::Blue);
+  }
+};
+
+template <> struct MappingTraits<Baby> {
+  static void mapping(IO &io, Baby &info) {
+    io.mapRequired("name", info.Name);
+    io.mapRequired("color", info.Color);
+  }
+};
+
+template <> struct MappingTraits<Animal> {
+  static void mapping(IO &io, Animal &info) {
+    io.mapRequired("name", info.Name);
+    io.mapOptional("age", info.Age);
+    io.mapOptional("babies", info.Babies);
+  }
+};
+
+} // namespace yaml
+} // namespace llvm
+
+TEST(ObjectYAMLGenerateSchema, SimpleSchema) {
+  std::string String;
+  raw_string_ostream OS(String);
+  std::vector<Animal> Animals;
+  yaml::GenerateSchema Gen(OS);
+  Gen << Animals;
+  StringRef YAMLSchema = R"(---
+type:            array
+items:
+  - type:            object
+    required:        name
+    properties:
+      name:
+        type:            string
+      age:
+        type:            string
+      babies:
+        type:            array
+        items:
+          - type:            object
+            required:
+              - name
+              - color
+            properties:
+              name:
+                type:            string
+              color:
+                type:            string
+                enum:
+                  - white
+                  - black
+                  - blue
+...
+)";
+  EXPECT_EQ(String.c_str(), YAMLSchema.str());
+}
diff --git a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
index fe7ff6ef68f99..69fd34d944825 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
@@ -166,6 +166,7 @@ static_library("Support") {
     "WithColor.cpp",
     "YAMLParser.cpp",
     "YAMLTraits.cpp",
+    "YAMLGenerateSchema.cpp",
     "Z3Solver.cpp",
     "circular_raw_ostream.cpp",
     "raw_os_ostream.cpp",



More information about the llvm-commits mailing list