diff --git a/llvm/docs/YamlIO.rst b/llvm/docs/YamlIO.rst --- a/llvm/docs/YamlIO.rst +++ b/llvm/docs/YamlIO.rst @@ -551,6 +551,34 @@ } }; +There are circumstances where we want to allow the entire mapping to be +read as an enumeration. For example, say some configuration option +started as an enumeration. Then it got more complex so it is now a +mapping. But it is necessary to support the old configuration files. +In that case, add a function ``enumInput`` like for +``ScalarEnumerationTraits::enumeration``. Examples: + +.. code-block:: c++ + + struct FooBarEnum { + int Foo; + int Bar; + bool operator==(const FooBarEnum &R) const { + return Foo == R.Foo && Bar == R.Bar; + } + }; + + template <> struct MappingTraits { + static void enumInput(IO &io, FooBarEnum &Val) { + io.enumCase(Val, "OnlyFoo", FooBarEnum({1, 0})); + io.enumCase(Val, "OnlyBar", FooBarEnum({0, 1})); + } + static void mapping(IO &io, FooBarEnum &Val) { + io.mapOptional("Foo", Val.Foo); + io.mapOptional("Bar", Val.Bar); + } + }; + No Normalization ---------------- diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -63,6 +63,7 @@ // static void mapping(IO &io, T &fields); // Optionally may provide: // static std::string validate(IO &io, T &fields); + // static void enumInput(IO &io, T &value); // // The optional flow flag will cause generated YAML to use a flow mapping // (e.g. { a: 0, b: 1 }): @@ -446,6 +447,31 @@ static bool const value = (sizeof(test>(nullptr)) == 1); }; +// Test if MappingContextTraits::enumInput() is defined on type T. +template struct has_MappingEnumInputTraits { + using Signature_validate = void (*)(class IO &, T &); + + template + static char test(SameType *); + + template static double test(...); + + static bool const value = + (sizeof(test>(nullptr)) == 1); +}; + +// Test if MappingTraits::enumInput() is defined on type T. +template struct has_MappingEnumInputTraits { + using Signature_validate = void (*)(class IO &, T &); + + template + static char test(SameType *); + + template static double test(...); + + static bool const value = (sizeof(test>(nullptr)) == 1); +}; + // Test if SequenceTraits is defined on type T. template struct has_SequenceMethodTraits @@ -1061,20 +1087,45 @@ io.endMapping(); } +#define MAPPING_COMMON() \ + do { \ + if (has_FlowTraits>::value) { \ + io.beginFlowMapping(); \ + detail::doMapping(io, Val, Ctx); \ + io.endFlowMapping(); \ + } else { \ + io.beginMapping(); \ + detail::doMapping(io, Val, Ctx); \ + io.endMapping(); \ + } \ + } while (false) + template -std::enable_if_t::value, void> +std::enable_if_t::value && + has_MappingEnumInputTraits::value, + void> yamlize(IO &io, T &Val, bool, Context &Ctx) { - if (has_FlowTraits>::value) { - io.beginFlowMapping(); - detail::doMapping(io, Val, Ctx); - io.endFlowMapping(); - } else { - io.beginMapping(); - detail::doMapping(io, Val, Ctx); - io.endMapping(); + if (!io.outputting()) { + io.beginEnumScalar(); + MappingTraits::enumInput(io, Val); + bool Matched = !io.matchEnumFallback(); + io.endEnumScalar(); + if (Matched) + return; } + MAPPING_COMMON(); } +template +std::enable_if_t::value && + !has_MappingEnumInputTraits::value, + void> +yamlize(IO &io, T &Val, bool, Context &Ctx) { + MAPPING_COMMON(); +} + +#undef MAPPING_COMMON + template std::enable_if_t::value, void> yamlize(IO &io, T &Val, bool, EmptyContext &Ctx) { diff --git a/llvm/unittests/Support/YAMLIOTest.cpp b/llvm/unittests/Support/YAMLIOTest.cpp --- a/llvm/unittests/Support/YAMLIOTest.cpp +++ b/llvm/unittests/Support/YAMLIOTest.cpp @@ -238,6 +238,58 @@ } } +// +// Test reading the entire struct as an enum. +// + +struct FooBarEnum { + int Foo; + int Bar; + bool operator==(const FooBarEnum &R) const { + return Foo == R.Foo && Bar == R.Bar; + } +}; + +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void enumInput(IO &io, FooBarEnum &Val) { + io.enumCase(Val, "OnlyFoo", FooBarEnum({1, 0})); + io.enumCase(Val, "OnlyBar", FooBarEnum({0, 1})); + } + static void mapping(IO &io, FooBarEnum &Val) { + io.mapOptional("Foo", Val.Foo); + io.mapOptional("Bar", Val.Bar); + } +}; +} // namespace yaml +} // namespace llvm + +TEST(YAMLIO, TestMapEnumRead) { + FooBarEnum Doc; + { + Input Yin("OnlyFoo"); + Yin >> Doc; + EXPECT_FALSE(Yin.error()); + EXPECT_EQ(Doc.Foo, 1); + EXPECT_EQ(Doc.Bar, 0); + } + { + Input Yin("OnlyBar"); + Yin >> Doc; + EXPECT_FALSE(Yin.error()); + EXPECT_EQ(Doc.Foo, 0); + EXPECT_EQ(Doc.Bar, 1); + } + { + Input Yin("{Foo: 3, Bar: 5}"); + Yin >> Doc; + EXPECT_FALSE(Yin.error()); + EXPECT_EQ(Doc.Foo, 3); + EXPECT_EQ(Doc.Bar, 5); + } +} + // // Test YAML filename handling. //