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
This commit is contained in:
Valentin Shergin
2019-04-04 12:32:41 -07:00
committed by Facebook Github Bot
parent cabc9d1ab3
commit 0fb27a7633
2 changed files with 238 additions and 5 deletions

View File

@@ -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);

View File

@@ -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 <typename T>
std::string getDebugName(T const &object) {
return "Node";
}
template <typename T>
std::string getDebugValue(T const &object) {
return "";
}
template <typename T>
std::vector<T> getDebugChildren(T const &object) {
return {};
}
template <typename T>
std::vector<T> getDebugProps(T const &object) {
return {};
}
template <typename T>
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 <typename T>
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 <typename T>
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 + "</" + nameString + ">");
}
/*
* 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<T>`
template <typename T>
std::string getDebugName(std::vector<T> const &vector) {
return "List";
}
template <typename T>
std::vector<T> getDebugChildren(std::vector<T> 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