From 0fb27a7633e64cf2fa58802a57aba6e946362679 Mon Sep 17 00:00:00 2001 From: Valentin Shergin Date: Thu, 4 Apr 2019 12:32:41 -0700 Subject: [PATCH] Fabric: *Informal* `DebugStringConvertible` interface Summary: Informal `DebugStringConvertible` interface serves the same purpose as `DebugStringConvertible` abstract class (providing universal pretty-printing interface) but relies on C++ static overloading instead of virtual dispatch. This approach has some advantages and disadvantages: Pros: * It's more clear and scoped. It's simpler to implement debug-printing functionality aside of normal production code. * It's more composable. * It allows print types that are not classes. * For some classes using `DebugStringConvertible` means that we have to use virtual inheritance (which might be undesirable and affect *production* performance (if a compiler isn't smart enough to remove empty base class). * For some highly lean classes (that designed to be as small as possible) adding base (even empty-in-prod) classes kinda... smells. Cons: The particular implementations cannot rely on dynamic dispatch which can complicate printing classes with meaningful differences between sampling classes (e.g. ShadowNode subclasses). That's why we don't remove `DebugStringConvertible` class yet. Reviewed By: mdvacca Differential Revision: D14715081 fbshipit-source-id: 1d397dbf81dc6d1dff0cc3f64ad09f10afe0085d --- .../fabric/debug/DebugStringConvertible.cpp | 13 + .../fabric/debug/DebugStringConvertible.h | 230 +++++++++++++++++- 2 files changed, 238 insertions(+), 5 deletions(-) diff --git a/ReactCommon/fabric/debug/DebugStringConvertible.cpp b/ReactCommon/fabric/debug/DebugStringConvertible.cpp index 59ce2e6c02f..951d5c27fe9 100644 --- a/ReactCommon/fabric/debug/DebugStringConvertible.cpp +++ b/ReactCommon/fabric/debug/DebugStringConvertible.cpp @@ -78,6 +78,19 @@ std::string DebugStringConvertible::getDebugDescription( DebugStringConvertibleOptions options) const { auto nameString = getDebugName(); auto valueString = getDebugValue(); + + // Convention: + // If `name` and `value` are empty, `description` is also empty. + if (nameString.empty() && valueString.empty()) { + return ""; + } + + // Convention: + // If `name` is empty and `value` isn't empty, `description` equals `value`. + if (nameString.empty()) { + return valueString; + } + auto childrenString = getDebugChildrenDescription(options); auto propsString = getDebugPropsDescription(options); diff --git a/ReactCommon/fabric/debug/DebugStringConvertible.h b/ReactCommon/fabric/debug/DebugStringConvertible.h index f1892605791..0337896116a 100644 --- a/ReactCommon/fabric/debug/DebugStringConvertible.h +++ b/ReactCommon/fabric/debug/DebugStringConvertible.h @@ -34,10 +34,16 @@ struct DebugStringConvertibleOptions { int maximumDepth{INT_MAX}; }; -// Abstract class describes conformance to DebugStringConvertible concept -// and implements basic recursive debug string assembly algorithm. -// Use this as a base class for providing a debugging textual representation -// of your class. +/* + * Abstract class describes conformance to DebugStringConvertible concept + * and implements basic recursive debug string assembly algorithm. + * Use this as a base class for providing a debugging textual representation + * of your class. + * + * The `DebugStringConvertible` *class* is obsolete. Whenever possible prefer + * implementing standalone functions that conform to the informal + * `DebugStringConvertible`-like interface instead of extending this class. + */ class DebugStringConvertible { public: virtual ~DebugStringConvertible() = default; @@ -83,7 +89,8 @@ class DebugStringConvertible {}; #if RN_DEBUG_STRING_CONVERTIBLE /* - * Set of particular-format-opinionated functions that convert base types to `std::string`; practically incapsulate `folly:to<>` and `folly::format`. + * Set of particular-format-opinionated functions that convert base types to + * `std::string`; practically incapsulate `folly:to<>` and `folly::format`. */ std::string toString(std::string const &value); std::string toString(int const &value); @@ -92,6 +99,219 @@ std::string toString(float const &value); std::string toString(double const &value); std::string toString(void const *value); +/* + * *Informal* `DebugStringConvertible` interface. + * + * The interface consts of several functions which are designed to be composable + * and reusable relying on C++ overloading mechanism. Implement appropriate + * versions of those functions for your custom type to enable conformance to the + * interface: + * + * - `getDebugName`: Returns a name of the object. Default implementation + * returns "Node". + * + * - `getDebugValue`: Returns a value assosiate with the object. Default + * implementation returns an empty string. + * + * - `getDebugChildren`: Returns a list of `DebugStringConvertible`-compatible + * objects which can be considered as *children* of the object. Default + * implementation returns an empty list. + * + * - `getDebugProps`: Returns a list of `DebugStringConvertible` objects which + * can be considered as *properties* of the object. Default implementation + * returns an empty list. + * + * - `getDebugDescription`: Returns a string which represents the object in a + * human-readable way. Default implementation returns a description of the + * subtree rooted at this node, represented in XML-like format using functions + * above to form the tree. + */ + +/* + * Universal implementation of `getDebugDescription`-family functions for all + * types. + */ +template +std::string getDebugName(T const &object) { + return "Node"; +} + +template +std::string getDebugValue(T const &object) { + return ""; +} + +template +std::vector getDebugChildren(T const &object) { + return {}; +} + +template +std::vector getDebugProps(T const &object) { + return {}; +} + +template +std::string getDebugPropsDescription( + T const &object, + DebugStringConvertibleOptions options) { + if (options.depth >= options.maximumDepth) { + return ""; + } + + std::string propsString = ""; + + options.depth++; + + for (auto prop : getDebugProps(object)) { + auto name = getDebugName(prop); + auto value = getDebugValue(prop); + auto children = getDebugPropsDescription(prop, options); + auto valueAndChildren = + value + (children.empty() ? "" : "(" + children + ")"); + propsString += + " " + name + (valueAndChildren.empty() ? "" : "=" + valueAndChildren); + } + + if (!propsString.empty()) { + // Removing leading space character. + propsString.erase(propsString.begin()); + } + + return propsString; +} + +template +std::string getDebugChildrenDescription( + T const &object, + DebugStringConvertibleOptions options) { + if (options.depth >= options.maximumDepth) { + return ""; + } + + auto trailing = options.format ? std::string{"\n"} : std::string{""}; + auto childrenString = std::string{""}; + options.depth++; + + for (auto child : getDebugChildren(object)) { + childrenString += getDebugDescription(child, options) + trailing; + } + + if (!childrenString.empty() && !trailing.empty()) { + // Removing trailing fragment. + childrenString.erase(childrenString.end() - 1); + } + + return childrenString; +} + +template +std::string getDebugDescription( + T const &object, + DebugStringConvertibleOptions options = {}) { + auto nameString = getDebugName(object); + auto valueString = getDebugValue(object); + + // Convention: + // If `name` and `value` are empty, `description` is also empty. + if (nameString.empty() && valueString.empty()) { + return ""; + } + + // Convention: + // If `name` is empty and `value` isn't empty, `description` equals `value`. + if (nameString.empty()) { + return valueString; + } + + auto childrenString = getDebugChildrenDescription(object, options); + auto propsString = getDebugPropsDescription(object, options); + + auto leading = + options.format ? std::string(options.depth * 2, ' ') : std::string{""}; + auto trailing = options.format ? std::string{"\n"} : std::string{""}; + + return leading + "<" + nameString + + (valueString.empty() ? "" : "=" + valueString) + + (propsString.empty() ? "" : " " + propsString) + + (childrenString.empty() ? "/>" + : ">" + trailing + childrenString + trailing + + leading + ""); +} + +/* + * Functions of `getDebugDescription`-family for primitive types. + */ +// `int` +inline std::string getDebugDescription( + int number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `float` +inline std::string getDebugDescription( + float number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `double` +inline std::string getDebugDescription( + double number, + DebugStringConvertibleOptions options = {}) { + return toString(number); +} + +// `bool` +inline std::string getDebugDescription( + bool boolean, + DebugStringConvertibleOptions options = {}) { + return toString(boolean); +} + +// `void *` +inline std::string getDebugDescription( + void const *pointer, + DebugStringConvertibleOptions options = {}) { + return toString(pointer); +} + +// `std::string` +inline std::string getDebugDescription( + std::string const &string, + DebugStringConvertibleOptions options = {}) { + return string; +} + +// `std::vector` +template +std::string getDebugName(std::vector const &vector) { + return "List"; +} + +template +std::vector getDebugChildren(std::vector const &vector) { + return vector; +} + +/* + * Trivial container for `name` and `value` pair that supports + * static `DebugStringConvertible` informal interface. + */ +struct DebugStringConvertibleObject { + std::string name; + std::string value; +}; + +inline std::string getDebugName(DebugStringConvertibleObject const &object) { + return object.name; +} + +inline std::string getDebugValue(DebugStringConvertibleObject const &object) { + return object.value; +} + #endif } // namespace react