[clang-tools-extra] [llvm] [clang-tidy] Introduced new option --dump-yaml-schema (PR #164412)

Timur Golubovich via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 21 05:54:51 PDT 2025


https://github.com/tgs-sc created https://github.com/llvm/llvm-project/pull/164412

Introduced a way for generating YAML schema for validating input YAML. This can be useful to see the full structure of the input YAML file for clang-tidy tool. This PR consists of 3 main commits: commit that introduces new YamlIO GenerateSchema and adds only necessary changes to YAMLTraits.h to leave it in working state, commit that adds main changes to YAMLTraits.h that add capabilities such as type names in ScalarTraits and final commit that adds an option to clang-tidy with some simple changes such as changing `YamlIO.outputting()` -> `YamlIO.getKind()`. I have an RFC with this topic: https://discourse.llvm.org/t/rfc-yamlgenerateschema-support-for-producing-yaml-schemas/85846.

>From 7d5630d978ae59ac09b320575c9779d5c5e96d7f 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 1/3] [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 | 400 ++++++++++++++++++
 llvm/include/llvm/Support/YAMLTraits.h        |  71 ++--
 llvm/lib/Support/CMakeLists.txt               |   1 +
 llvm/lib/Support/YAMLGenerateSchema.cpp       | 284 +++++++++++++
 llvm/lib/Support/YAMLTraits.cpp               |   4 +
 llvm/unittests/Support/CMakeLists.txt         |   1 +
 .../Support/YAMLGenerateSchemaTest.cpp        | 124 ++++++
 .../gn/secondary/llvm/lib/Support/BUILD.gn    |   1 +
 8 files changed, 861 insertions(+), 25 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..ac1609a9ee469
--- /dev/null
+++ b/llvm/include/llvm/Support/YAMLGenerateSchema.h
@@ -0,0 +1,400 @@
+//===- 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/Casting.h"
+#include "llvm/Support/YAMLTraits.h"
+
+namespace llvm {
+
+namespace json {
+class Value;
+}
+
+namespace yaml {
+
+class GenerateSchema : public IO {
+public:
+  GenerateSchema(raw_ostream &RO);
+  ~GenerateSchema() override = default;
+
+  IOKind getKind() const override;
+  bool outputting() const override;
+  bool mapTag(StringRef, bool) override;
+  void beginMapping() override;
+  void endMapping() override;
+  bool preflightKey(StringRef, 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(StringRef, bool) override;
+  bool matchEnumFallback() override;
+  void endEnumScalar() override;
+  bool beginBitSetScalar(bool &) override;
+  bool bitSetMatch(StringRef, 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;
+
+  bool preflightDocument();
+  void postflightDocument();
+
+  class SchemaNode {
+  public:
+    virtual json::Value toJSON() const = 0;
+
+    virtual ~SchemaNode() = default;
+  };
+
+  enum class PropertyKind : uint8_t {
+    UserDefined,
+    Properties,
+    AdditionalProperties,
+    Required,
+    Optional,
+    Type,
+    Enum,
+    Items,
+    FlowStyle,
+  };
+
+  class SchemaProperty : public SchemaNode {
+    StringRef Name;
+    PropertyKind Kind;
+
+  public:
+    SchemaProperty(StringRef Name, PropertyKind Kind)
+        : Name(Name), Kind(Kind) {}
+
+    PropertyKind getKind() const { return Kind; }
+
+    StringRef getName() const { return Name; }
+  };
+
+  class Schema;
+
+  class UserDefinedProperty final : public SchemaProperty {
+    Schema *Value;
+
+  public:
+    UserDefinedProperty(StringRef Name, Schema *Value)
+        : SchemaProperty(Name, PropertyKind::UserDefined), Value(Value) {}
+
+    Schema *getSchema() const { return Value; }
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::UserDefined;
+    }
+  };
+
+  class PropertiesProperty final : public SchemaProperty,
+                                   SmallVector<UserDefinedProperty *, 8> {
+  public:
+    using BaseVector = SmallVector<UserDefinedProperty *, 8>;
+
+    PropertiesProperty()
+        : SchemaProperty("properties", PropertyKind::Properties) {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Properties;
+    }
+  };
+
+  class AdditionalPropertiesProperty final : public SchemaProperty {
+    Schema *Value;
+
+  public:
+    AdditionalPropertiesProperty(Schema *Value = nullptr)
+        : SchemaProperty("additionalProperties",
+                         PropertyKind::AdditionalProperties),
+          Value(Value) {}
+
+    Schema *getSchema() const { return Value; }
+
+    void setSchema(Schema *S) { Value = S; }
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::AdditionalProperties;
+    }
+  };
+
+  class RequiredProperty final : public SchemaProperty,
+                                 SmallVector<StringRef, 4> {
+  public:
+    using BaseVector = SmallVector<StringRef, 4>;
+
+    RequiredProperty() : SchemaProperty("required", PropertyKind::Required) {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Required;
+    }
+  };
+
+  class OptionalProperty final : public SchemaProperty,
+                                 SmallVector<StringRef, 4> {
+  public:
+    using BaseVector = SmallVector<StringRef, 4>;
+
+    OptionalProperty() : SchemaProperty("optional", PropertyKind::Optional) {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Optional;
+    }
+  };
+
+  class TypeProperty final : public SchemaProperty {
+    StringRef Value;
+
+  public:
+    TypeProperty(StringRef Value)
+        : SchemaProperty("type", PropertyKind::Type), Value(Value) {}
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Type;
+    }
+  };
+
+  class EnumProperty final : public SchemaProperty, SmallVector<StringRef, 4> {
+  public:
+    using BaseVector = SmallVector<StringRef, 4>;
+
+    EnumProperty() : SchemaProperty("enum", PropertyKind::Enum) {}
+
+    using BaseVector::begin;
+    using BaseVector::emplace_back;
+    using BaseVector::end;
+    using BaseVector::size;
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Enum;
+    }
+  };
+
+  class ItemsProperty final : public SchemaProperty {
+    Schema *Value;
+
+  public:
+    ItemsProperty(Schema *Value = nullptr)
+        : SchemaProperty("items", PropertyKind::Items), Value(Value) {}
+
+    Schema *getSchema() const { return Value; }
+
+    void setSchema(Schema *S) { Value = S; }
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::Items;
+    }
+  };
+
+  enum class FlowStyle : bool {
+    Block,
+    Flow,
+  };
+
+  class FlowStyleProperty final : public SchemaProperty {
+    FlowStyle Style;
+
+  public:
+    FlowStyleProperty(FlowStyle Style = FlowStyle::Block)
+        : SchemaProperty("flowStyle", PropertyKind::FlowStyle), Style(Style) {}
+
+    void setStyle(FlowStyle S) { Style = S; }
+
+    FlowStyle getStyle() const { return Style; }
+
+    json::Value toJSON() const override;
+
+    static bool classof(const SchemaProperty *Property) {
+      return Property->getKind() == PropertyKind::FlowStyle;
+    }
+  };
+
+  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;
+
+    json::Value toJSON() const override;
+  };
+
+private:
+  std::vector<std::unique_ptr<SchemaNode>> SchemaNodes;
+  SmallVector<Schema *, 8> Schemas;
+  raw_ostream &RO;
+  SchemaNode *Root = nullptr;
+
+  template <typename PropertyType, typename... PropertyArgs>
+  PropertyType *createProperty(PropertyArgs &&...Args) {
+    auto UPtr =
+        std::make_unique<PropertyType>(std::forward<PropertyArgs>(Args)...);
+    auto *Ptr = UPtr.get();
+    SchemaNodes.emplace_back(std::move(UPtr));
+    return Ptr;
+  }
+
+  template <typename PropertyType, typename... PropertyArgs>
+  PropertyType *getOrCreateProperty(Schema &S, PropertyArgs... Args) {
+    auto Found = std::find_if(S.begin(), S.end(), [](SchemaProperty *Property) {
+      return isa<PropertyType>(Property);
+    });
+    if (Found != S.end()) {
+      return cast<PropertyType>(*Found);
+    }
+    PropertyType *Created =
+        createProperty<PropertyType>(std::forward<PropertyArgs>(Args)...);
+    S.emplace_back(Created);
+    return Created;
+  }
+
+  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();
+  }
+};
+
+// 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.preflightDocument();
+  yamlize(Gen, DocumentListTraits<T>::element(Gen, DocList, 0), true, Ctx);
+  Gen.postflightDocument();
+  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.preflightDocument();
+  yamlize(Gen, Map, true, Ctx);
+  Gen.postflightDocument();
+  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.preflightDocument();
+  yamlize(Gen, Seq, true, Ctx);
+  Gen.postflightDocument();
+  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.preflightDocument();
+  yamlize(Gen, Val, true, Ctx);
+  Gen.postflightDocument();
+  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.preflightDocument();
+  yamlize(Gen, Val, true, Ctx);
+  Gen.postflightDocument();
+  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.preflightDocument();
+  yamlize(Gen, Val, true, Ctx);
+  Gen.postflightDocument();
+  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 3d36f41ca1a04..435323ef9ffbd 100644
--- a/llvm/include/llvm/Support/YAMLTraits.h
+++ b/llvm/include/llvm/Support/YAMLTraits.h
@@ -683,12 +683,19 @@ struct unvalidatedMappingTraits
                                 !has_MappingValidateTraits<T, Context>::value> {
 };
 
+enum class IOKind : uint8_t {
+  Outputting,
+  Inputting,
+  GeneratingSchema,
+};
+
 // Base class for Input and Output.
 class LLVM_ABI IO {
 public:
   IO(void *Ctxt = nullptr);
   virtual ~IO();
 
+  virtual IOKind getKind() const = 0;
   virtual bool outputting() const = 0;
 
   virtual unsigned beginSequence() = 0;
@@ -732,7 +739,8 @@ class LLVM_ABI IO {
   virtual void setAllowUnknownKeys(bool Allow);
 
   template <typename T> void enumCase(T &Val, StringRef Str, const T ConstVal) {
-    if (matchEnumScalar(Str, outputting() && Val == ConstVal)) {
+    if (matchEnumScalar(Str,
+                        getKind() == IOKind::Outputting && Val == ConstVal)) {
       Val = ConstVal;
     }
   }
@@ -740,7 +748,8 @@ class LLVM_ABI IO {
   // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF
   template <typename T>
   void enumCase(T &Val, StringRef Str, const uint32_t ConstVal) {
-    if (matchEnumScalar(Str, outputting() && Val == static_cast<T>(ConstVal))) {
+    if (matchEnumScalar(Str, getKind() == IOKind::Outputting &&
+                                 Val == static_cast<T>(ConstVal))) {
       Val = ConstVal;
     }
   }
@@ -757,7 +766,8 @@ class LLVM_ABI IO {
 
   template <typename T>
   void bitSetCase(T &Val, StringRef Str, const T ConstVal) {
-    if (bitSetMatch(Str, outputting() && (Val & ConstVal) == ConstVal)) {
+    if (bitSetMatch(Str, getKind() == IOKind::Outputting &&
+                             (Val & ConstVal) == ConstVal)) {
       Val = static_cast<T>(Val | ConstVal);
     }
   }
@@ -765,21 +775,24 @@ class LLVM_ABI IO {
   // allow anonymous enum values to be used with LLVM_YAML_STRONG_TYPEDEF
   template <typename T>
   void bitSetCase(T &Val, StringRef 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, StringRef 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, StringRef 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;
   }
 
@@ -844,7 +857,8 @@ class LLVM_ABI 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);
@@ -905,7 +919,7 @@ 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::Outputting) {
     SmallString<128> Storage;
     raw_svector_ostream Buffer(Storage);
     ScalarTraits<T>::output(Val, io.getContext(), Buffer);
@@ -924,7 +938,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::Outputting) {
     std::string Storage;
     raw_string_ostream Buffer(Storage);
     BlockScalarTraits<T>::output(Val, YamlIO.getContext(), Buffer);
@@ -943,7 +957,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::Outputting) {
     std::string ScalarStorage, TagStorage;
     raw_string_ostream ScalarBuffer(ScalarStorage), TagBuffer(TagStorage);
     TaggedScalarTraits<T>::output(Val, io.getContext(), ScalarBuffer,
@@ -985,7 +999,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";
@@ -993,7 +1007,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);
@@ -1007,7 +1021,7 @@ yamlize(IO &io, T &Val, bool, Context &Ctx) {
 template <typename T, typename Context>
 bool yamlizeMappingEnumInput(IO &io, T &Val) {
   if constexpr (has_MappingEnumInputTraits<T, Context>::value) {
-    if (io.outputting())
+    if (io.getKind() == IOKind::Outputting)
       return false;
 
     io.beginEnumScalar();
@@ -1038,7 +1052,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::Outputting) {
     io.beginMapping();
     CustomMappingTraits<T>::output(io, Val);
     io.endMapping();
@@ -1053,8 +1067,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::Outputting
+              ? PolymorphicTraits<T>::getKind(Val)
+              : io.getNodeKind()) {
   case NodeKind::Scalar:
     return yamlize(io, PolymorphicTraits<T>::getAsScalar(Val), true, Ctx);
   case NodeKind::Map:
@@ -1075,7 +1090,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)) {
@@ -1086,7 +1103,9 @@ yamlize(IO &io, T &Seq, bool, Context &Ctx) {
     io.endFlowSequence();
   } 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)) {
@@ -1239,7 +1258,7 @@ struct ScalarBitSetTraits<
 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 {
       BufPtr = new (&Buffer) TNorm(io);
@@ -1247,7 +1266,7 @@ template <typename TNorm, typename TFinal> struct MappingNormalization {
   }
 
   ~MappingNormalization() {
-    if (!io.outputting()) {
+    if (io.getKind() != IOKind::Outputting) {
       Result = BufPtr->denormalize(io);
     }
     BufPtr->~TNorm();
@@ -1269,7 +1288,7 @@ template <typename TNorm, typename TFinal> struct MappingNormalization {
 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) {
       BufPtr = allocator->Allocate<TNorm>();
@@ -1280,7 +1299,7 @@ template <typename TNorm, typename TFinal> struct MappingNormalizationHeap {
   }
 
   ~MappingNormalizationHeap() {
-    if (io.outputting()) {
+    if (io.getKind() == IOKind::Outputting) {
       BufPtr->~TNorm();
     } else {
       Result = BufPtr->denormalize(io);
@@ -1327,6 +1346,7 @@ class LLVM_ABI 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;
@@ -1478,6 +1498,7 @@ class LLVM_ABI 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;
@@ -1563,8 +1584,8 @@ void IO::processKeyWithDefault(StringRef 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)) {
@@ -1574,7 +1595,7 @@ void IO::processKeyWithDefault(StringRef 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 42b21b5e62029..0dd16356bf7f6 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -281,6 +281,7 @@ add_llvm_component_library(LLVMSupport
   WithColor.cpp
   YAMLParser.cpp
   YAMLTraits.cpp
+  YAMLGenerateSchema.cpp
   raw_os_ostream.cpp
   raw_ostream.cpp
   raw_ostream_proxy.cpp
diff --git a/llvm/lib/Support/YAMLGenerateSchema.cpp b/llvm/lib/Support/YAMLGenerateSchema.cpp
new file mode 100644
index 0000000000000..3f323869eacee
--- /dev/null
+++ b/llvm/lib/Support/YAMLGenerateSchema.cpp
@@ -0,0 +1,284 @@
+//===- 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"
+#include "llvm/Support/JSON.h"
+
+using namespace llvm;
+using namespace yaml;
+
+//===----------------------------------------------------------------------===//
+//  GenerateSchema
+//===----------------------------------------------------------------------===//
+
+GenerateSchema::GenerateSchema(raw_ostream &RO) : RO(RO) {}
+
+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 = createProperty<TypeProperty>("object");
+  Top->emplace_back(Type);
+  auto *FlowStyle = createProperty<FlowStyleProperty>();
+  Top->emplace_back(FlowStyle);
+}
+
+void GenerateSchema::endMapping() {}
+
+bool GenerateSchema::preflightKey(StringRef Key, bool Required,
+                                  bool SameAsDefault, bool &UseDefault,
+                                  void *&SaveInfo) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  if (Key == "additionalProperties") {
+    auto *Additional = getOrCreateProperty<AdditionalPropertiesProperty>(*Top);
+    auto *S = createSchema();
+    Additional->setSchema(S);
+    Schemas.push_back(S);
+    return true;
+  }
+
+  if (Required) {
+    auto *Req = getOrCreateProperty<RequiredProperty>(*Top);
+    Req->emplace_back(Key);
+  } else {
+    auto *Opt = getOrCreateProperty<OptionalProperty>(*Top);
+    Opt->emplace_back(Key);
+  }
+  auto *Properties = getOrCreateProperty<PropertiesProperty>(*Top);
+  auto *S = createSchema();
+  auto *UserDefined = createProperty<UserDefinedProperty>(Key, S);
+  Properties->emplace_back(UserDefined);
+  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() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = createProperty<TypeProperty>("object");
+  Top->emplace_back(Type);
+  auto *FlowStyle = createProperty<FlowStyleProperty>(FlowStyle::Flow);
+  Top->emplace_back(FlowStyle);
+}
+
+void GenerateSchema::endFlowMapping() {}
+
+unsigned GenerateSchema::beginSequence() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = createProperty<TypeProperty>("array");
+  Top->emplace_back(Type);
+  auto *Items = createProperty<ItemsProperty>();
+  Top->emplace_back(Items);
+  auto *FlowStyle = createProperty<FlowStyleProperty>();
+  Top->emplace_back(FlowStyle);
+  return 1;
+}
+
+void GenerateSchema::endSequence() {}
+
+bool GenerateSchema::preflightElement(unsigned, void *&) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *S = createSchema();
+  auto *Items = getOrCreateProperty<ItemsProperty>(*Top);
+  Items->setSchema(S);
+  Schemas.push_back(S);
+  return true;
+}
+
+void GenerateSchema::postflightElement(void *) {
+  assert(!Schemas.empty());
+  Schemas.pop_back();
+}
+
+unsigned GenerateSchema::beginFlowSequence() {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = createProperty<TypeProperty>("array");
+  Top->emplace_back(Type);
+  auto *Items = createProperty<ItemsProperty>();
+  Top->emplace_back(Items);
+  auto *FlowStyle = createProperty<FlowStyleProperty>(FlowStyle::Flow);
+  Top->emplace_back(FlowStyle);
+  return 1;
+}
+
+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 = createProperty<TypeProperty>("string");
+  Top->emplace_back(Type);
+  auto *Enum = createProperty<EnumProperty>();
+  Top->emplace_back(Enum);
+}
+
+bool GenerateSchema::matchEnumScalar(StringRef 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(StringRef Val, bool Arg) {
+  return matchEnumScalar(Val, Arg);
+}
+
+void GenerateSchema::endBitSetScalar() { endEnumScalar(); }
+
+void GenerateSchema::scalarString(StringRef &Val, QuotingType) {
+  auto *Top = getTopSchema();
+  assert(Top);
+  auto *Type = createProperty<TypeProperty>("string");
+  Top->emplace_back(Type);
+}
+
+void GenerateSchema::blockScalarString(StringRef &S) {
+  scalarString(S, QuotingType::None);
+}
+
+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.
+
+bool GenerateSchema::preflightDocument() {
+  auto *S = createSchema();
+  Root = S;
+  Schemas.push_back(S);
+  return true;
+}
+
+void GenerateSchema::postflightDocument() {
+  assert(!Schemas.empty());
+  Schemas.pop_back();
+  json::Value JSONValue = Root->toJSON();
+  RO << llvm::formatv("{0:2}\n", JSONValue);
+}
+
+json::Value GenerateSchema::UserDefinedProperty::toJSON() const {
+  return Value->toJSON();
+}
+
+json::Value GenerateSchema::PropertiesProperty::toJSON() const {
+  json::Object JSONObject;
+  for (auto *Property : *this) {
+    json::Value JSONValue = Property->toJSON();
+    JSONObject.try_emplace(Property->getName().data(), std::move(JSONValue));
+  }
+  return JSONObject;
+}
+
+json::Value GenerateSchema::AdditionalPropertiesProperty::toJSON() const {
+  return Value->toJSON();
+}
+
+
+json::Value GenerateSchema::RequiredProperty::toJSON() const {
+  json::Array JSONArray;
+  for (auto Value : *this) {
+    json::Value JSONValue(Value.data());
+    JSONArray.emplace_back(std::move(JSONValue));
+  }
+  return JSONArray;
+}
+
+json::Value GenerateSchema::OptionalProperty::toJSON() const {
+  json::Array JSONArray;
+  for (auto Value : *this) {
+    json::Value JSONValue(Value.data());
+    JSONArray.emplace_back(std::move(JSONValue));
+  }
+  return JSONArray;
+}
+
+json::Value GenerateSchema::TypeProperty::toJSON() const {
+  json::Value JSONValue(Value.data());
+  return JSONValue;
+}
+
+json::Value GenerateSchema::EnumProperty::toJSON() const {
+  json::Array JSONArray;
+  for (auto Value : *this) {
+    json::Value JSONValue(Value.data());
+    JSONArray.emplace_back(std::move(JSONValue));
+  }
+  return JSONArray;
+}
+
+json::Value GenerateSchema::ItemsProperty::toJSON() const {
+  return Value->toJSON();
+}
+
+json::Value GenerateSchema::FlowStyleProperty::toJSON() const {
+  StringRef Value;
+  switch (Style) {
+  case FlowStyle::Block:
+    Value = "block";
+    break;
+  case FlowStyle::Flow:
+    Value = "flow";
+    break;
+  default:
+    llvm_unreachable("index out of bounds");
+    break;
+  }
+  json::Value JSONValue(Value.data());
+  return JSONValue;
+}
+
+json::Value GenerateSchema::Schema::toJSON() const {
+  json::Object JSONObject;
+  for (auto Value : *this) {
+    json::Value JSONValue = Value->toJSON();
+    StringRef Key = Value->getName();
+    JSONObject.try_emplace(Key.data(), std::move(JSONValue));
+  }
+  return JSONObject;
+}
diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp
index 95a41eafdf5e4..bea05378ac8f3 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 21f10eb610f11..850661de95264 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -112,6 +112,7 @@ add_llvm_unittest(SupportTests
   VirtualOutputFileTest.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..b5a10ea9f4861
--- /dev/null
+++ b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
@@ -0,0 +1,124 @@
+//===- 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"({
+  "flowStyle": "block",
+  "items": {
+    "flowStyle": "block",
+    "optional": [
+      "age",
+      "babies"
+    ],
+    "properties": {
+      "age": {
+        "type": "string"
+      },
+      "babies": {
+        "flowStyle": "block",
+        "items": {
+          "flowStyle": "block",
+          "properties": {
+            "color": {
+              "enum": [
+                "white",
+                "black",
+                "blue"
+              ],
+              "type": "string"
+            },
+            "name": {
+              "type": "string"
+            }
+          },
+          "required": [
+            "name",
+            "color"
+          ],
+          "type": "object"
+        },
+        "type": "array"
+      },
+      "name": {
+        "type": "string"
+      }
+    },
+    "required": [
+      "name"
+    ],
+    "type": "object"
+  },
+  "type": "array"
+}
+)";
+  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 38ba4661daacc..a70f943e1fa45 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Support/BUILD.gn
@@ -177,6 +177,7 @@ static_library("Support") {
     "WithColor.cpp",
     "YAMLParser.cpp",
     "YAMLTraits.cpp",
+    "YAMLGenerateSchema.cpp",
     "Z3Solver.cpp",
     "circular_raw_ostream.cpp",
     "raw_os_ostream.cpp",

>From 4f715eebf179c1c3d24884ab22452d98179f2239 Mon Sep 17 00:00:00 2001
From: Timur Golubovich <timur.golubovich at syntacore.com>
Date: Mon, 20 Oct 2025 17:47:25 +0000
Subject: [PATCH 2/3] [llvm][Support] Introduced scalar type names in
 YAMLGenerateSchema

Since now, when creating a YAML Schema, the type of all scalars is a
'string', this may be a bit strange. For example if you start typing
number in IDE, it will complain that 'string' was expected. This
patch fixes it by introducing new optional TypeNameTrait. Is is
optional in order not to break backward compatibility.
---
 llvm/include/llvm/Support/YAMLTraits.h        | 67 +++++++++++++++++--
 llvm/lib/Support/YAMLGenerateSchema.cpp       |  3 +-
 .../Support/YAMLGenerateSchemaTest.cpp        |  2 +-
 3 files changed, 65 insertions(+), 7 deletions(-)

diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h
index 435323ef9ffbd..f92e26e6424a1 100644
--- a/llvm/include/llvm/Support/YAMLTraits.h
+++ b/llvm/include/llvm/Support/YAMLTraits.h
@@ -145,6 +145,7 @@ enum class QuotingType { None, Single, Double };
 ///        return StringRef();
 ///      }
 ///      static QuotingType mustQuote(StringRef) { return QuotingType::Single; }
+///      static constexpr StringRef typeName = "string";
 ///    };
 template <typename T, typename Enable = void> struct ScalarTraits {
   // Must provide:
@@ -158,6 +159,9 @@ template <typename T, typename Enable = void> struct ScalarTraits {
   //
   // Function to determine if the value should be quoted.
   // static QuotingType mustQuote(StringRef);
+  //
+  // Optional, for GeneratingSchema:
+  // static constexpr StringRef typeName = "string";
 };
 
 /// This class should be specialized by type that requires custom conversion
@@ -175,6 +179,7 @@ template <typename T, typename Enable = void> struct ScalarTraits {
 ///        // return empty string on success, or error string
 ///        return StringRef();
 ///      }
+///      static constexpr StringRef typeName = "string";
 ///    };
 template <typename T> struct BlockScalarTraits {
   // Must provide:
@@ -189,6 +194,7 @@ template <typename T> struct BlockScalarTraits {
   // Optional:
   // static StringRef inputTag(T &Val, std::string Tag)
   // static void outputTag(const T &Val, raw_ostream &Out)
+  // static constexpr StringRef typeName = "string";
 };
 
 /// This class should be specialized by type that requires custom conversion
@@ -211,6 +217,7 @@ template <typename T> struct BlockScalarTraits {
 ///      static QuotingType mustQuote(const MyType &Value, StringRef) {
 ///        return QuotingType::Single;
 ///      }
+///      static constexpr StringRef typeName = "integer";
 ///    };
 template <typename T> struct TaggedScalarTraits {
   // Must provide:
@@ -226,6 +233,9 @@ template <typename T> struct TaggedScalarTraits {
   //
   // Function to determine if the value should be quoted.
   // static QuotingType mustQuote(const T &Value, StringRef Scalar);
+  //
+  // Optional:
+  // static constexpr StringRef typeName = "string";
 };
 
 /// This class should be specialized by any type that needs to be converted
@@ -442,6 +452,14 @@ template <class T> struct has_CustomMappingTraits {
       is_detected<check, CustomMappingTraits<T>>::value;
 };
 
+// Test if typeName is defined on type T.
+template <typename T> struct has_TypeNameTraits {
+  template <class U>
+  using check = std::is_same<decltype(&U::typeName), StringRef>;
+
+  static constexpr bool value = is_detected<check, T>::value;
+};
+
 // Test if flow is defined on type T.
 template <typename T> struct has_FlowTraits {
   template <class U> using check = decltype(&U::flow);
@@ -925,13 +943,19 @@ std::enable_if_t<has_ScalarTraits<T>::value, void> yamlize(IO &io, T &Val, bool,
     ScalarTraits<T>::output(Val, io.getContext(), Buffer);
     StringRef Str = Buffer.str();
     io.scalarString(Str, ScalarTraits<T>::mustQuote(Str));
-  } else {
+  } else if (io.getKind() == IOKind::Inputting) {
     StringRef Str;
     io.scalarString(Str, ScalarTraits<T>::mustQuote(Str));
     StringRef Result = ScalarTraits<T>::input(Str, io.getContext(), Val);
     if (!Result.empty()) {
       io.setError(Twine(Result));
     }
+  } else {
+    StringRef TypeName = "string";
+    if constexpr (has_TypeNameTraits<ScalarTraits<T>>::value) {
+      TypeName = ScalarTraits<T>::typeName;
+    }
+    io.scalarString(TypeName, QuotingType::None);
   }
 }
 
@@ -944,13 +968,19 @@ yamlize(IO &YamlIO, T &Val, bool, EmptyContext &Ctx) {
     BlockScalarTraits<T>::output(Val, YamlIO.getContext(), Buffer);
     StringRef Str(Storage);
     YamlIO.blockScalarString(Str);
-  } else {
+  } else if (YamlIO.getKind() == IOKind::Inputting) {
     StringRef Str;
     YamlIO.blockScalarString(Str);
     StringRef Result =
         BlockScalarTraits<T>::input(Str, YamlIO.getContext(), Val);
     if (!Result.empty())
       YamlIO.setError(Twine(Result));
+  } else {
+    StringRef TypeName = "string";
+    if constexpr (has_TypeNameTraits<ScalarTraits<T>>::value) {
+      TypeName = ScalarTraits<T>::typeName;
+    }
+    YamlIO.blockScalarString(TypeName);
   }
 }
 
@@ -966,7 +996,7 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
     StringRef ScalarStr(ScalarStorage);
     io.scalarString(ScalarStr,
                     TaggedScalarTraits<T>::mustQuote(Val, ScalarStr));
-  } else {
+  } else if (io.getKind() == IOKind::Inputting) {
     std::string Tag;
     io.scalarTag(Tag);
     StringRef Str;
@@ -976,6 +1006,12 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
     if (!Result.empty()) {
       io.setError(Twine(Result));
     }
+  } else {
+    StringRef TypeName = "string";
+    if constexpr (has_TypeNameTraits<ScalarTraits<T>>::value) {
+      TypeName = ScalarTraits<T>::typeName;
+    }
+    io.scalarString(TypeName, QuotingType::None);
   }
 }
 
@@ -1056,11 +1092,15 @@ yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) {
     io.beginMapping();
     CustomMappingTraits<T>::output(io, Val);
     io.endMapping();
-  } else {
+  } else if (io.getKind() == IOKind::Inputting) {
     io.beginMapping();
     for (StringRef key : io.keys())
       CustomMappingTraits<T>::inputOne(io, key, Val);
     io.endMapping();
+  } else {
+    io.beginMapping();
+    CustomMappingTraits<T>::inputOne(io, "additionalProperties", Val);
+    io.endMapping();
   }
 }
 
@@ -1121,6 +1161,7 @@ template <> struct ScalarTraits<bool> {
   LLVM_ABI static void output(const bool &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, bool &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "boolean";
 };
 
 template <> struct ScalarTraits<StringRef> {
@@ -1139,60 +1180,70 @@ template <> struct ScalarTraits<uint8_t> {
   LLVM_ABI static void output(const uint8_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, uint8_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<uint16_t> {
   LLVM_ABI static void output(const uint16_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, uint16_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<uint32_t> {
   LLVM_ABI static void output(const uint32_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, uint32_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<uint64_t> {
   LLVM_ABI static void output(const uint64_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, uint64_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<int8_t> {
   LLVM_ABI static void output(const int8_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, int8_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<int16_t> {
   LLVM_ABI static void output(const int16_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, int16_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<int32_t> {
   LLVM_ABI static void output(const int32_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, int32_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<int64_t> {
   LLVM_ABI static void output(const int64_t &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, int64_t &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<float> {
   LLVM_ABI static void output(const float &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, float &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "number";
 };
 
 template <> struct ScalarTraits<double> {
   LLVM_ABI static void output(const double &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, double &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "number";
 };
 
 // For endian types, we use existing scalar Traits class for the underlying
@@ -1220,6 +1271,10 @@ struct ScalarTraits<support::detail::packed_endian_specific_integral<
   static QuotingType mustQuote(StringRef Str) {
     return ScalarTraits<value_type>::mustQuote(Str);
   }
+
+  static constexpr StringRef typeName = has_TypeNameTraits<value_type>::value
+                                            ? ScalarTraits<value_type>::typeName
+                                            : "string";
 };
 
 template <typename value_type, llvm::endianness endian, size_t alignment>
@@ -1652,24 +1707,28 @@ template <> struct ScalarTraits<Hex8> {
   LLVM_ABI static void output(const Hex8 &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, Hex8 &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<Hex16> {
   LLVM_ABI static void output(const Hex16 &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, Hex16 &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<Hex32> {
   LLVM_ABI static void output(const Hex32 &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, Hex32 &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<Hex64> {
   LLVM_ABI static void output(const Hex64 &, void *, raw_ostream &);
   LLVM_ABI static StringRef input(StringRef, void *, Hex64 &);
   static QuotingType mustQuote(StringRef) { return QuotingType::None; }
+  static constexpr StringRef typeName = "integer";
 };
 
 template <> struct ScalarTraits<VersionTuple> {
diff --git a/llvm/lib/Support/YAMLGenerateSchema.cpp b/llvm/lib/Support/YAMLGenerateSchema.cpp
index 3f323869eacee..5bf508f4c7cc0 100644
--- a/llvm/lib/Support/YAMLGenerateSchema.cpp
+++ b/llvm/lib/Support/YAMLGenerateSchema.cpp
@@ -167,7 +167,7 @@ void GenerateSchema::endBitSetScalar() { endEnumScalar(); }
 void GenerateSchema::scalarString(StringRef &Val, QuotingType) {
   auto *Top = getTopSchema();
   assert(Top);
-  auto *Type = createProperty<TypeProperty>("string");
+  auto *Type = createProperty<TypeProperty>(Val);
   Top->emplace_back(Type);
 }
 
@@ -219,7 +219,6 @@ json::Value GenerateSchema::AdditionalPropertiesProperty::toJSON() const {
   return Value->toJSON();
 }
 
-
 json::Value GenerateSchema::RequiredProperty::toJSON() const {
   json::Array JSONArray;
   for (auto Value : *this) {
diff --git a/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
index b5a10ea9f4861..d76b5298145a9 100644
--- a/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
+++ b/llvm/unittests/Support/YAMLGenerateSchemaTest.cpp
@@ -81,7 +81,7 @@ TEST(ObjectYAMLGenerateSchema, SimpleSchema) {
     ],
     "properties": {
       "age": {
-        "type": "string"
+        "type": "integer"
       },
       "babies": {
         "flowStyle": "block",

>From 79716399d6c99964e901d067fd75315fb2606fda Mon Sep 17 00:00:00 2001
From: Timur Golubovich <timur.golubovich at syntacore.com>
Date: Mon, 20 Oct 2025 17:47:45 +0000
Subject: [PATCH 3/3] [clang-tidy] Introduced new option --dump-yaml-schema

Since YAML Generate Schema was added, it can be used to dump
current clang-tidy's YAML's schema.
---
 .../clang-tidy/ClangTidyOptions.cpp           | 21 ++++++++++++++++---
 .../clang-tidy/ClangTidyOptions.h             |  3 +++
 .../clang-tidy/tool/ClangTidyMain.cpp         | 11 ++++++++++
 3 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
index b752a9beb0e34..b168e0dd28ddd 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/MemoryBufferRef.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/YAMLGenerateSchema.h"
 #include "llvm/Support/YAMLTraits.h"
 #include <algorithm>
 #include <optional>
@@ -87,7 +88,7 @@ struct NOptionMap {
 template <>
 void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
              EmptyContext &Ctx) {
-  if (IO.outputting()) {
+  if (IO.getKind() == IOKind::Outputting) {
     // Ensure check options are sorted
     std::vector<std::pair<StringRef, StringRef>> SortedOptions;
     SortedOptions.reserve(Val.size());
@@ -108,7 +109,7 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
       IO.postflightKey(SaveInfo);
     }
     IO.endMapping();
-  } else {
+  } else if (IO.getKind() == IOKind::Inputting) {
     // We need custom logic here to support the old method of specifying check
     // options using a list of maps containing key and value keys.
     auto &I = reinterpret_cast<Input &>(IO);
@@ -128,6 +129,11 @@ void yamlize(IO &IO, ClangTidyOptions::OptionMap &Val, bool,
     } else {
       IO.setError("expected a sequence or map");
     }
+  } else {
+    MappingNormalization<NOptionMap, ClangTidyOptions::OptionMap> NOpts(IO,
+                                                                        Val);
+    EmptyContext Ctx;
+    yamlize(IO, NOpts->Options, true, Ctx);
   }
 }
 
@@ -182,7 +188,7 @@ struct ChecksVariant {
 };
 
 template <> void yamlize(IO &IO, ChecksVariant &Val, bool, EmptyContext &Ctx) {
-  if (!IO.outputting()) {
+  if (IO.getKind() == IOKind::Inputting) {
     // Special case for reading from YAML
     // Must support reading from both a string or a list
     auto &I = reinterpret_cast<Input &>(IO);
@@ -195,6 +201,9 @@ template <> void yamlize(IO &IO, ChecksVariant &Val, bool, EmptyContext &Ctx) {
     } else {
       IO.setError("expected string or sequence");
     }
+  } else if (IO.getKind() == IOKind::GeneratingSchema) {
+    Val.AsVector = std::vector<std::string>();
+    yamlize(IO, *Val.AsVector, true, Ctx);
   }
 }
 
@@ -541,6 +550,12 @@ parseConfiguration(llvm::MemoryBufferRef Config) {
   return Options;
 }
 
+void dumpConfigurationYAMLSchema(llvm::raw_fd_ostream &Stream) {
+  ClangTidyOptions Options;
+  llvm::yaml::GenerateSchema GS(Stream);
+  GS << Options;
+}
+
 static void diagHandlerImpl(const llvm::SMDiagnostic &Diag, void *Ctx) {
   (*reinterpret_cast<DiagCallback *>(Ctx))(Diag);
 }
diff --git a/clang-tools-extra/clang-tidy/ClangTidyOptions.h b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
index 2aae92f1d9eb3..f0aa710c685a2 100644
--- a/clang-tools-extra/clang-tidy/ClangTidyOptions.h
+++ b/clang-tools-extra/clang-tidy/ClangTidyOptions.h
@@ -343,6 +343,9 @@ std::error_code parseLineFilter(llvm::StringRef LineFilter,
 llvm::ErrorOr<ClangTidyOptions>
 parseConfiguration(llvm::MemoryBufferRef Config);
 
+/// Dumps configuration YAML Schema to \p Stream
+void dumpConfigurationYAMLSchema(llvm::raw_fd_ostream &Stream);
+
 using DiagCallback = llvm::function_ref<void(const llvm::SMDiagnostic &)>;
 
 llvm::ErrorOr<ClangTidyOptions>
diff --git a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
index 64157f530b8c0..7c6fa7f5c40b9 100644
--- a/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
+++ b/clang-tools-extra/clang-tidy/tool/ClangTidyMain.cpp
@@ -355,6 +355,12 @@ see https://clang.llvm.org/extra/clang-tidy/QueryBasedCustomChecks.html.
                                               cl::init(false),
                                               cl::cat(ClangTidyCategory));
 
+static cl::opt<bool> DumpYAMLSchema("dump-yaml-schema", desc(R"(
+Dumps configuration YAML Schema in JSON format to
+stdout.
+)"),
+                                    cl::init(false),
+                                    cl::cat(ClangTidyCategory));
 namespace clang::tidy {
 
 static void printStats(const ClangTidyStats &Stats) {
@@ -684,6 +690,11 @@ int clangTidyMain(int argc, const char **argv) {
     return 0;
   }
 
+  if (DumpYAMLSchema) {
+    dumpConfigurationYAMLSchema(llvm::outs());
+    return 0;
+  }
+
   if (VerifyConfig) {
     std::vector<ClangTidyOptionsProvider::OptionsSource> RawOptions =
         OptionsProvider->getRawOptions(FileName);



More information about the llvm-commits mailing list