diff --git a/llvm/include/llvm/Support/YAMLTraits.h b/llvm/include/llvm/Support/YAMLTraits.h index 0f57f44340c2..98964fc00200 100644 --- a/llvm/include/llvm/Support/YAMLTraits.h +++ b/llvm/include/llvm/Support/YAMLTraits.h @@ -323,6 +323,7 @@ public: virtual bool preflightElement(unsigned, void *&) = 0; virtual void postflightElement(void*) = 0; virtual void endSequence() = 0; + virtual bool canElideEmptySequence() = 0; virtual unsigned beginFlowSequence() = 0; virtual bool preflightFlowElement(unsigned, void *&) = 0; @@ -388,7 +389,7 @@ public: typename llvm::enable_if_c::value,void>::type mapOptional(const char* Key, T& Val) { // omit key/value instead of outputting empty sequence - if ( this->outputting() && !(Val.begin() != Val.end()) ) + if ( this->canElideEmptySequence() && !(Val.begin() != Val.end()) ) return; this->processKey(Key, Val, false); } @@ -715,6 +716,7 @@ private: virtual void endBitSetScalar(); virtual void scalarString(StringRef &); virtual void setError(const Twine &message); + virtual bool canElideEmptySequence(); class HNode { public: @@ -837,7 +839,7 @@ public: virtual void endBitSetScalar(); virtual void scalarString(StringRef &); virtual void setError(const Twine &message); - + virtual bool canElideEmptySequence(); public: // These are only used by operator<<. They could be private // if that templated operator could be made a friend. diff --git a/llvm/lib/Support/YAMLTraits.cpp b/llvm/lib/Support/YAMLTraits.cpp index b0cd41518637..ae7f7dcb0d05 100644 --- a/llvm/lib/Support/YAMLTraits.cpp +++ b/llvm/lib/Support/YAMLTraits.cpp @@ -334,6 +334,10 @@ void Input::setError(const Twine &Message) { this->setError(CurrentNode, Message); } +bool Input::canElideEmptySequence() { + return false; +} + Input::MapHNode::~MapHNode() { for (MapHNode::NameToNode::iterator i = Mapping.begin(), End = Mapping.end(); i != End; ++i) { @@ -532,6 +536,19 @@ void Output::scalarString(StringRef &S) { void Output::setError(const Twine &message) { } +bool Output::canElideEmptySequence() { + // Normally, with an optional key/value where the value is an empty sequence, + // the whole key/value can be not written. But, that produces wrong yaml + // if the key/value is the only thing in the map and the map is used in + // a sequence. This detects if the this sequence is the first key/value + // in map that itself is embedded in a sequnce. + if (StateStack.size() < 2) + return true; + if (StateStack.back() != inMapFirstKey) + return true; + return (StateStack[StateStack.size()-2] != inSeq); +} + void Output::output(StringRef s) { Column += s.size(); Out << s; diff --git a/llvm/unittests/Support/YAMLIOTest.cpp b/llvm/unittests/Support/YAMLIOTest.cpp index 0993d8c0b555..eedc5842b633 100644 --- a/llvm/unittests/Support/YAMLIOTest.cpp +++ b/llvm/unittests/Support/YAMLIOTest.cpp @@ -1297,3 +1297,66 @@ TEST(YAMLIO, TestReadBuiltInTypesHex64Error) { EXPECT_TRUE(yin.error()); } +struct OptionalTest { + std::vector Numbers; +}; + +struct OptionalTestSeq { + std::vector Tests; +}; + +LLVM_YAML_IS_SEQUENCE_VECTOR(OptionalTest); +namespace llvm { +namespace yaml { + template <> + struct MappingTraits { + static void mapping(IO& IO, OptionalTest &OT) { + IO.mapOptional("Numbers", OT.Numbers); + } + }; + + template <> + struct MappingTraits { + static void mapping(IO &IO, OptionalTestSeq &OTS) { + IO.mapOptional("Tests", OTS.Tests); + } + }; +} +} + +TEST(YAMLIO, SequenceElideTest) { + // Test that writing out a purely optional structure with its fields set to + // default followed by other data is properly read back in. + OptionalTestSeq Seq; + OptionalTest One, Two, Three, Four; + int N[] = {1, 2, 3}; + Three.Numbers.assign(N, N + 3); + Seq.Tests.push_back(One); + Seq.Tests.push_back(Two); + Seq.Tests.push_back(Three); + Seq.Tests.push_back(Four); + + std::string intermediate; + { + llvm::raw_string_ostream ostr(intermediate); + Output yout(ostr); + yout << Seq; + } + + Input yin(intermediate); + OptionalTestSeq Seq2; + yin >> Seq2; + + EXPECT_FALSE(yin.error()); + + EXPECT_EQ(4UL, Seq2.Tests.size()); + + EXPECT_TRUE(Seq2.Tests[0].Numbers.empty()); + EXPECT_TRUE(Seq2.Tests[1].Numbers.empty()); + + EXPECT_EQ(1, Seq2.Tests[2].Numbers[0]); + EXPECT_EQ(2, Seq2.Tests[2].Numbers[1]); + EXPECT_EQ(3, Seq2.Tests[2].Numbers[2]); + + EXPECT_TRUE(Seq2.Tests[3].Numbers.empty()); +}