This is an archive of the discontinued LLVM Phabricator instance.

[ADT] Add relation operators for Optional
ClosedPublic

Authored by timshen on Aug 4 2016, 1:31 PM.

Details

Summary

Make Optional's behavior the same as the coming std::optional.

Diff Detail

Repository
rL LLVM

Event Timeline

timshen updated this revision to Diff 66852.Aug 4 2016, 1:31 PM
timshen retitled this revision from to [ADT] Add relation operators for Optional.
timshen updated this object.
timshen added a reviewer: dblaikie.
timshen added a subscriber: llvm-commits.
dblaikie added inline comments.Aug 4 2016, 2:28 PM
include/llvm/ADT/Optional.h
155 ↗(On Diff #66852)

I'd probably just use if (X && Y) ? (similarly elsewhere)

unittests/ADT/OptionalTest.cpp
380 ↗(On Diff #66852)

This seems to have lost the tests for comparison with NoneType (as distinct from the tests with Optional<T>s that are in the non-value state)

But in general - while this is obviously more compact than writing out all the tests explicitly, I wonder if there's a better pivot or other way we could write these tests to be more legible and/or more compact. Did you have any other ideas/things you attempted for testing? as a very rough sketch I'd probably start by wanting to have a test case/function per operator, then figure out how to factor the common functionality of those tests into utilities/helpers. Maybe templates..

timshen updated this revision to Diff 66863.Aug 4 2016, 3:23 PM

Split testcases.

timshen marked 2 inline comments as done.Aug 4 2016, 3:24 PM
timshen added inline comments.
include/llvm/ADT/Optional.h
155 ↗(On Diff #66863)

Done. For the context that enforces a cast, I used X.hasValue() instead of bool(X); in other cases I used implicit conversion.

timshen updated this revision to Diff 66864.Aug 4 2016, 3:25 PM
timshen marked an inline comment as done.

Format.

dblaikie edited edge metadata.Aug 5 2016, 2:22 PM

Still trying to figure out how to make the test cases both short and legible - open to ideas, there's no need to take my suggestions as decree or anything.

unittests/ADT/OptionalTest.cpp
388–396 ↗(On Diff #66864)

Nice way to get the extra coverage over the asymmetric comparisons!

408–411 ↗(On Diff #66864)

I don't think we need to test this whole matrix. Looks like for the equality operator we only need 4 cases (each case containing the 5 cases the CheckOptRelations contains): {None, None}, {None, 3}, {3, None}, {3, 3}, {3, 4}). I'm not sure we need more even for the inequality operators, I think we get coverage for operator<, with say: {None, None}, {None, 3}, {3, None}, {3, 4}, {4, 3} - I suppose {3, 3}.

so, yeah, drop the top right and bottom left corners (because 4 and 3 are equivalent WRT None, but not WRT each other... )

Hmm, wonder what the best way to describe this is...

How would it look if we removed the loops from CheckOptRelations and it just took two Optionals, the relation (via the template parameter), and whether it should be true.

Then each test should consist of 5 lines that perhaps more clearly show the two operands, the operation, and the truthiness:

CheckOptRelations<EqualTo>(None, None, true);
CheckOptRelations<EqualTo>(None, Three, false);
CheckOptRelations<EqualTo>(Three, None, false);
CheckOptRelations<EqualTo>(Three, Three, true);
CheckOptRelations<EqualTo>(Three, Four, false);
CheckOptRelations<EqualTo>(Four, Three, false); // could skip this one when testing equality/inequality

Maybe it can be made more terse (just shorten the name of CheckOptRelations perhaps - it'll be used a lot in a short scope so should be pretty obvious what it does)

Does any of that seem better/reasonable?

I suppose alternatively the test utility could take the comparison once, then an initializer_list of {val, val, expected result} tuples (write a little struct to wrap them). Still ensures that the test cases are fairly self explanatory, which comparison is being tested and what result is expected.

timshen added inline comments.Aug 8 2016, 5:18 PM
unittests/ADT/OptionalTest.cpp
408–411 ↗(On Diff #66864)

I don't think we need to test this whole matrix. Looks like for the equality operator we only need 4 cases (each case containing the 5 cases the CheckOptRelations contains): {None, None}, {None, 3}, {3, None}, {3, 3}, {3, 4}). I'm not sure we need more even for the inequality operators, I think we get coverage for operator<, with say: {None, None}, {None, 3}, {3, None}, {3, 4}, {4, 3} - I suppose {3, 3}.

Yeah I think 9 cases overlap each other a bit. I can remove duplicated cases, but then we (including the future readers) have to reason about the exhaustiveness of test cases in order to make sure of the coverage, where the 9 cases matrix is so obviously exhaustive.

In exhaustiveness's perspective your suggestion seems less comprehensible.

dblaikie added inline comments.Aug 9 2016, 2:15 PM
unittests/ADT/OptionalTest.cpp
408–411 ↗(On Diff #66864)

For myself it raised the opposite question "what's all this testing - are there cases/complexities in the code that I hadn't seen"/"why are there so many 'interesting' cases here"?

Hmm, maybe we could simplify it a bit if we created a noisy type - ie: we shouldn't need to test {3, 3}, {3, 4}, etc... - that's just the underlying type's operator. If we test that it calls the underlying type's operator when both values are non-empty, that should be sufficient. Then we can test the None cases.

That'd still be all the 3 variants of each of {None, None}, {None, Something}, {Something, None}, {Something, Something}.

Happy to chat in person/IRC/pair program up some possible ways this might look - might be faster. (& maybe there's an existing similar ADT in LLVM & we could see how it tests such a matrix? (but my first few attempts to look - APInt/Float didn't find anything useful to draw a parallel from))

timshen updated this revision to Diff 67591.Aug 10 2016, 1:24 PM
timshen edited edge metadata.

As discussed offline, hand craft mock objects to precisely test the std::optional specification.

timshen marked an inline comment as done.Aug 10 2016, 1:24 PM

This seems a bit more complicated than what we were discussing - perhaps it's for good reason. But to give a sense, this is sort of what I was picturing.

(some notes:

  • I don't think it's worth checking that comparisons with None do not invoke the underlying type's comparison operators - yes, it's a possible bug, but seems like the sort of excess testing that's easy to fall into with mocking because we can
  • did find one 'bug' in the implementation, op==(const T&, const Optional<T>&) inverted its arguments and called op==(const Optional<T>&, const T&) which isn't technically how it's spec'd, but one could argue the spec is overly narrow. I'm kind of OK 'fixing' that and having the test be a bit more narrow for simplicity...
  • It's still annoying to have the EXPECT lines in another function since gtest doesn't give a backtrace on the EXPECT failure. But there's nothing really to be done about that short of writing out all the EXPECT lines, which seems lame/annoying/difficult/verbose/not worth it. But all the more reason that splitting this into one test per operator like this is at least a little helpful.)

    template <typename OperatorT, typename T> void CheckRelation(const Optional<T> &L, const Optional<T> &R, bool Expected) { EXPECT_EQ(Expected, OperatorT::apply(L, R));

    if (L) EXPECT_EQ(Expected, OperatorT::apply(*L, R)); else EXPECT_EQ(Expected, OperatorT::apply(None, R));

    if (R) EXPECT_EQ(Expected, OperatorT::apply(L, *R)); else EXPECT_EQ(Expected, OperatorT::apply(L, None)); }
struct EqualityMock {};

const Optional<EqualityMock> NoneEq, EqualityLhs((EqualityMock())),
    EqualityRhs((EqualityMock()));
bool IsEqual;

bool operator==(const EqualityMock &Lhs, const EqualityMock &Rhs) {
  EXPECT_EQ(&Lhs, &*EqualityLhs);
  EXPECT_EQ(&Rhs, &*EqualityRhs);
  return IsEqual;
}

TEST_F(OptionalTest, OperatorEqual) {
  CheckRelation<EqualTo>(NoneEq, NoneEq, true);
  CheckRelation<EqualTo>(NoneEq, EqualityLhs, false);
  CheckRelation<EqualTo>(EqualityLhs, NoneEq, false);

  IsEqual = false;
  CheckRelation<EqualTo>(EqualityLhs, EqualityRhs, false);
  IsEqual = true;
  CheckRelation<EqualTo>(EqualityLhs, EqualityRhs, true);
}

TEST_F(OptionalTest, OperatorNotEqual) {
  CheckRelation<NotEqualTo>(NoneEq, NoneEq, false);
  CheckRelation<NotEqualTo>(NoneEq, EqualityLhs, true);
  CheckRelation<NotEqualTo>(EqualityLhs, NoneEq, true);

  IsEqual = false;
  CheckRelation<NotEqualTo>(EqualityLhs, EqualityRhs, true);
  IsEqual = true;
  CheckRelation<NotEqualTo>(EqualityLhs, EqualityRhs, false);
}

struct InequalityMock { };

const Optional<InequalityMock> NoneIneq, InequalityLhs((InequalityMock())),
    InequalityRhs((InequalityMock()));
bool IsLess;

bool operator<(const InequalityMock &Lhs, const InequalityMock &Rhs) {
  EXPECT_EQ(&Lhs, &*InequalityLhs);
  EXPECT_EQ(&Rhs, &*InequalityRhs);
  return IsLess;
}

TEST_F(OptionalTest, OperatorLess) {
  CheckRelation<Less>(NoneIneq, NoneIneq, false);
  CheckRelation<Less>(NoneIneq, InequalityRhs, true);
  CheckRelation<Less>(InequalityLhs, NoneIneq, false);

  IsLess = false;
  CheckRelation<Less>(InequalityLhs, InequalityRhs, false);
  IsLess = true;
  CheckRelation<Less>(InequalityLhs, InequalityRhs, true);
}

TEST_F(OptionalTest, OperatorGreater) {
  CheckRelation<Greater>(NoneIneq, NoneIneq, false);
  CheckRelation<Greater>(NoneIneq, InequalityRhs, false);
  CheckRelation<Greater>(InequalityLhs, NoneIneq, true);

  IsLess = false;
  CheckRelation<Greater>(InequalityRhs, InequalityLhs, false);
  IsLess = true;
  CheckRelation<Greater>(InequalityRhs, InequalityLhs, true);
}

TEST_F(OptionalTest, OperatorLessEqual) {
  CheckRelation<LessEqual>(NoneIneq, NoneIneq, true);
  CheckRelation<LessEqual>(NoneIneq, InequalityRhs, true);
  CheckRelation<LessEqual>(InequalityLhs, NoneIneq, false);

  IsLess = false;
  CheckRelation<LessEqual>(InequalityRhs, InequalityLhs, true);
  IsLess = true;
  CheckRelation<LessEqual>(InequalityRhs, InequalityLhs, false);
}

TEST_F(OptionalTest, OperatorGreaterEqual) {
  CheckRelation<GreaterEqual>(NoneIneq, NoneIneq, true);
  CheckRelation<GreaterEqual>(NoneIneq, InequalityRhs, false);
  CheckRelation<GreaterEqual>(InequalityLhs, NoneIneq, true);

  IsLess = false;
  CheckRelation<GreaterEqual>(InequalityLhs, InequalityRhs, true);
  IsLess = true;
  CheckRelation<GreaterEqual>(InequalityLhs, InequalityRhs, false);
}
timshen updated this revision to Diff 67714.Aug 11 2016, 11:12 AM

Done.

I used non-literal for CheckRelation, e.g.
CheckRelation<Greater>(InequalityRhs, InequalityLhs, IsLess);

So that it seems more readable (read as Rhs is less then Lhs).

dblaikie accepted this revision.Aug 11 2016, 11:19 AM
dblaikie edited edge metadata.

Fair enough - I felt like using the boolean literal rather than referring to the global made it a bit simpler/clearer (& consistent with the None cases). The operation is already mentioned in the template parameter. But yeah - nothing's great here, totally up to you.

This revision is now accepted and ready to land.Aug 11 2016, 11:19 AM

The operation is already mentioned in the template parameter.

This is why it's closer to what we want to test - "Greater operator on Lhs and Rhs" is equivalent to "Rhs is less then Lhs".

This revision was automatically updated to reflect the committed changes.

Thanks for the review!