Commit 6555995a authored by Don Hinton's avatar Don Hinton
Browse files

[CommandLine] Add callbacks to Options

Summary:
Add a new cl::callback attribute to Option.

This attribute specifies a callback function that is called when
an option is seen, and can be used to set other options, as in
option A implies option B.  If the option is a `cl::list`, and
`cl::CommaSeparated` is also specified, the callback will fire
once for each value.  This could be used to validate combinations
or selectively set other options.

Reviewers: beanz, thomasfinch, MaskRay, thopre, serge-sans-paille

Reviewed By: beanz

Subscribers: llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D70620
parent 0a717d5b
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -996,6 +996,31 @@ This section describes the basic attributes that you can specify on options.
* The **cl::cat** attribute specifies the option category that the option
  belongs to. The category should be a `cl::OptionCategory`_ object.

.. _cl::callback:

* The **cl::callback** attribute specifies a callback function that is
  called when an option is seen, and can be used to set other options,
  as in option B implies option A.  If the option is a `cl::list`_,
  and `cl::CommaSeparated`_ is also specified, the callback will fire
  once for each value.  This could be used to validate combinations or
  selectively set other options.

  .. code-block:: c++

    cl::opt<bool> OptA("a", cl::desc("option a"));
    cl::opt<bool> OptB(
        "b", cl::desc("option b -- This option turns on option a"),
        cl::callback([&](const bool &) { OptA = true; }));
    cl::list<std::string, cl::list<std::string>> List(
      "list",
      cl::desc("option list -- This option turns on options a when "
               "'foo' is included in list"),
      cl::CommaSeparated,
      cl::callback([&](const std::string &Str) {
        if (Str == "foo")
          OptA = true;
      }));

Option Modifiers
----------------

+3 −0
Original line number Diff line number Diff line
@@ -90,6 +90,9 @@ Non-comprehensive list of changes in this release
  ``-cfguard-nochecks`` option. Note that this feature should always be used 
  with optimizations enabled.

* ``Callbacks`` have been added to ``CommandLine Options``.  These can
  be used to validate of selectively enable other options.

Changes to the LLVM IR
----------------------

+57 −0
Original line number Diff line number Diff line
@@ -471,6 +471,43 @@ struct sub {
  template <class Opt> void apply(Opt &O) const { O.addSubCommand(Sub); }
};

// Specify a callback function to be called when an option is seen.
// Can be used to set other options automatically.
template <typename R, typename Ty> struct cb {
  std::function<R(Ty)> CB;

  cb(std::function<R(Ty)> CB) : CB(CB) {}

  template <typename Opt> void apply(Opt &O) const { O.setCallback(CB); }
};

namespace detail {
template <typename F>
struct callback_traits : public callback_traits<decltype(&F::operator())> {};

template <typename R, typename C, typename... Args>
struct callback_traits<R (C::*)(Args...) const> {
  using result_type = R;
  using arg_type = typename std::tuple_element<0, std::tuple<Args...>>::type;
  static_assert(sizeof...(Args) == 1, "callback function must have one and only one parameter");
  static_assert(std::is_same<result_type, void>::value,
                "callback return type must be void");
  static_assert(
      std::is_lvalue_reference<arg_type>::value &&
          std::is_const<typename std::remove_reference<arg_type>::type>::value,
      "callback arg_type must be a const lvalue reference");
};
} // namespace detail

template <typename F>
cb<typename detail::callback_traits<F>::result_type,
   typename detail::callback_traits<F>::arg_type>
callback(F CB) {
  using result_type = typename detail::callback_traits<F>::result_type;
  using arg_type = typename detail::callback_traits<F>::arg_type;
  return cb<result_type, arg_type>(CB);
}

//===----------------------------------------------------------------------===//
// OptionValue class

@@ -1344,6 +1381,7 @@ class opt : public Option,
      return true; // Parse error!
    this->setValue(Val);
    this->setPosition(pos);
    Callback(Val);
    return false;
  }

@@ -1402,6 +1440,7 @@ public:

  template <class T> DataType &operator=(const T &Val) {
    this->setValue(Val);
    Callback(Val);
    return this->getValue();
  }

@@ -1411,6 +1450,14 @@ public:
    apply(this, Ms...);
    done();
  }

  void setCallback(
      std::function<void(const typename ParserClass::parser_data_type &)> CB) {
    Callback = CB;
  }

  std::function<void(const typename ParserClass::parser_data_type &)> Callback =
      [](const typename ParserClass::parser_data_type &) {};
};

extern template class opt<unsigned>;
@@ -1550,6 +1597,7 @@ class list : public Option, public list_storage<DataType, StorageClass> {
    list_storage<DataType, StorageClass>::addValue(Val);
    setPosition(pos);
    Positions.push_back(pos);
    Callback(Val);
    return false;
  }

@@ -1596,6 +1644,14 @@ public:
    apply(this, Ms...);
    done();
  }

  void setCallback(
      std::function<void(const typename ParserClass::parser_data_type &)> CB) {
    Callback = CB;
  }

  std::function<void(const typename ParserClass::parser_data_type &)> Callback =
      [](const typename ParserClass::parser_data_type &) {};
};

// multi_val - Modifier to set the number of additional values.
@@ -1696,6 +1752,7 @@ class bits : public Option, public bits_storage<DataType, Storage> {
    this->addValue(Val);
    setPosition(pos);
    Positions.push_back(pos);
    Callback(Val);
    return false;
  }

+64 −2
Original line number Diff line number Diff line
@@ -71,7 +71,7 @@ public:
  ~StackOption() override { this->removeArgument(); }

  template <class DT> StackOption<T> &operator=(const DT &V) {
    this->setValue(V);
    Base::operator=(V);
    return *this;
  }
};
@@ -1722,4 +1722,66 @@ TEST(CommandLineTest, OptionErrorMessageSuggest) {

  cl::ResetAllOptionOccurrences();
}

TEST(CommandLineTest, Callback) {
  cl::ResetCommandLineParser();

  StackOption<bool> OptA("a", cl::desc("option a"));
  StackOption<bool> OptB(
      "b", cl::desc("option b -- This option turns on option a"),
      cl::callback([&](const bool &) { OptA = true; }));
  StackOption<bool> OptC(
      "c", cl::desc("option c -- This option turns on options a and b"),
      cl::callback([&](const bool &) { OptB = true; }));
  StackOption<std::string, cl::list<std::string>> List(
      "list",
      cl::desc("option list -- This option turns on options a, b, and c when "
               "'foo' is included in list"),
      cl::CommaSeparated,
      cl::callback([&](const std::string &Str) {
        if (Str == "foo")
          OptC = true;
      }));

  const char *args1[] = {"prog", "-a"};
  EXPECT_TRUE(cl::ParseCommandLineOptions(2, args1));
  EXPECT_TRUE(OptA);
  EXPECT_FALSE(OptB);
  EXPECT_FALSE(OptC);
  EXPECT_TRUE(List.size() == 0);
  cl::ResetAllOptionOccurrences();

  const char *args2[] = {"prog", "-b"};
  EXPECT_TRUE(cl::ParseCommandLineOptions(2, args2));
  EXPECT_TRUE(OptA);
  EXPECT_TRUE(OptB);
  EXPECT_FALSE(OptC);
  EXPECT_TRUE(List.size() == 0);
  cl::ResetAllOptionOccurrences();

  const char *args3[] = {"prog", "-c"};
  EXPECT_TRUE(cl::ParseCommandLineOptions(2, args3));
  EXPECT_TRUE(OptA);
  EXPECT_TRUE(OptB);
  EXPECT_TRUE(OptC);
  EXPECT_TRUE(List.size() == 0);
  cl::ResetAllOptionOccurrences();

  const char *args4[] = {"prog", "--list=foo,bar"};
  EXPECT_TRUE(cl::ParseCommandLineOptions(2, args4));
  EXPECT_TRUE(OptA);
  EXPECT_TRUE(OptB);
  EXPECT_TRUE(OptC);
  EXPECT_TRUE(List.size() == 2);
  cl::ResetAllOptionOccurrences();

  const char *args5[] = {"prog", "--list=bar"};
  EXPECT_TRUE(cl::ParseCommandLineOptions(2, args5));
  EXPECT_FALSE(OptA);
  EXPECT_FALSE(OptB);
  EXPECT_FALSE(OptC);
  EXPECT_TRUE(List.size() == 1);

  cl::ResetAllOptionOccurrences();
}
} // anonymous namespace