Modify BaseFormatter to help static analysers
authorPhil Willoughby <philwill@fb.com>
Tue, 7 Feb 2017 08:45:01 +0000 (00:45 -0800)
committerFacebook Github Bot <facebook-github-bot@users.noreply.github.com>
Tue, 7 Feb 2017 08:50:20 +0000 (00:50 -0800)
Summary:
This decouples the format-string-parser from the storage of the arguments to be
formatted: a static analyser can now define a class which derives from
`BaseFormatter` with no storage and use `BaseFormatter::operator()` to verify
the syntax of the format string and determine which arguments will be
referenced.

This method of allowing overrides is resolved at compile time; the benchmarks
confirm that there is no run-time difference.

Reviewed By: yfeldblum

Differential Revision: D4507689

fbshipit-source-id: f109d81ae54dc074ac363720ef6e565520435d26

folly/Format-inl.h
folly/Format.h

index 1b998dec9a05559183399c4b16270e35f20db52a..70f5564d1a9faccf804a5e9213c8ebd8d0e9eb0a 100644 (file)
@@ -159,11 +159,7 @@ BaseFormatter<Derived, containerMode, Args...>::BaseFormatter(
     Args&&... args)
     : str_(str),
       values_(FormatValue<typename std::decay<Args>::type>(
-          std::forward<Args>(args))...) {
-  static_assert(
-      !containerMode || sizeof...(Args) == 1,
-      "Exactly one argument required in container mode");
-}
+          std::forward<Args>(args))...) {}
 
 template <class Derived, bool containerMode, class... Args>
 template <class Output>
@@ -246,7 +242,7 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(
               arg.widthIndex == FormatArg::kNoIndex,
               "cannot provide width arg index without value arg index");
           int sizeArg = nextArg++;
-          arg.width = getSizeArg(size_t(sizeArg), arg);
+          arg.width = asDerived().getSizeArg(size_t(sizeArg), arg);
         }
 
         argIndex = nextArg++;
@@ -256,7 +252,7 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(
           arg.enforce(
               arg.widthIndex != FormatArg::kNoIndex,
               "cannot provide value arg index without width arg index");
-          arg.width = getSizeArg(size_t(arg.widthIndex), arg);
+          arg.width = asDerived().getSizeArg(size_t(arg.widthIndex), arg);
         }
 
         try {
@@ -274,7 +270,7 @@ void BaseFormatter<Derived, containerMode, Args...>::operator()(
           "folly::format: may not have both default and explicit arg indexes");
     }
 
-    doFormat(size_t(argIndex), arg, out);
+    asDerived().template doFormat(size_t(argIndex), arg, out);
   }
 }
 
index f87d90a95f4d46a1885f7fcac5a1436cb298b94f..8986c4cac847e077a26341027770b549688ade6b 100644 (file)
@@ -56,9 +56,14 @@ class FormatterTag {};
  * this directly, you have to use format(...) below.
  */
 
-/* BaseFormatter class. Currently, the only behavior that can be
- * overridden is the actual formatting of positional parameters in
+/* BaseFormatter class.
+ * Overridable behaviours:
+ * You may override the actual formatting of positional parameters in
  * `doFormatArg`. The Formatter class provides the default implementation.
+ *
+ * You may also override `doFormat` and `getSizeArg`. These override points were
+ * added to permit static analysis of format strings, when it is inconvenient
+ * or impossible to instantiate a BaseFormatter with the correct storage
  */
 template <class Derived, bool containerMode, class... Args>
 class BaseFormatter {
@@ -108,6 +113,10 @@ class BaseFormatter {
       ValueTuple;
   static constexpr size_t valueCount = std::tuple_size<ValueTuple>::value;
 
+  Derived const& asDerived() const {
+    return *static_cast<const Derived*>(this);
+  }
+
   template <size_t K, class Callback>
   typename std::enable_if<K == valueCount>::type
   doFormatFrom(size_t i, FormatArg& arg, Callback& /*cb*/) const {
@@ -118,7 +127,7 @@ class BaseFormatter {
   typename std::enable_if<(K < valueCount)>::type
   doFormatFrom(size_t i, FormatArg& arg, Callback& cb) const {
     if (i == K) {
-      static_cast<const Derived*>(this)->template doFormatArg<K>(arg, cb);
+      asDerived().template doFormatArg<K>(arg, cb);
     } else {
       doFormatFrom<K + 1>(i, arg, cb);
     }
@@ -196,7 +205,11 @@ class Formatter : public BaseFormatter<
       : BaseFormatter<
             Formatter<containerMode, Args...>,
             containerMode,
-            Args...>(str, std::forward<Args>(args)...) {}
+            Args...>(str, std::forward<Args>(args)...) {
+    static_assert(
+        !containerMode || sizeof...(Args) == 1,
+        "Exactly one argument required in container mode");
+  }
 
   template <size_t K, class Callback>
   void doFormatArg(FormatArg& arg, Callback& cb) const {