-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Add a ToString function to create human-readable debug descriptions #2384
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
3215889
07758ea
0a1b145
3428867
ce8da11
4084c57
f39d23f
193301f
026f517
fadadb9
6b157c4
ea32eab
baff0e6
292cdcd
eaa8f5f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ | |
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_H_ | ||
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_IMMUTABLE_SORTED_MAP_H_ | ||
|
||
#include <type_traits> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Did you mean to include There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leftover, sorry. I tried adding some |
||
#include <utility> | ||
|
||
#include "Firestore/core/src/firebase/firestore/immutable/array_sorted_map.h" | ||
|
@@ -38,6 +39,8 @@ namespace immutable { | |
template <typename K, typename V, typename C = util::Comparator<K>> | ||
class SortedMap : public impl::SortedMapBase { | ||
public: | ||
using key_type = K; | ||
using mapped_type = V; | ||
/** The type of the entries stored in the map. */ | ||
using value_type = std::pair<K, V>; | ||
using array_type = impl::ArraySortedMap<K, V, C>; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
/* | ||
* Copyright 2019 Google | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
#ifndef FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_TO_STRING_H_ | ||
#define FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_TO_STRING_H_ | ||
|
||
#if !defined(__OBJC__) | ||
#error "This header only supports Objective-C++" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could split this into to_string.h and to_string_apple.h (or do some ifdef magic around the objc specificities). There's no real urgent need to do so, but it'd make this useful from the pure c++ world too. Optional. If you do decide to do this, doing this as a separate PR would be reasonable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't bother splitting. Just add Using a mechanism like the one using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done with |
||
#endif // !defined(__OBJC__) | ||
|
||
#import <Foundation/Foundation.h> | ||
|
||
#include <algorithm> | ||
#include <numeric> | ||
#include <string> | ||
#include <type_traits> | ||
#include <utility> | ||
|
||
#include "Firestore/core/src/firebase/firestore/util/string_apple.h" | ||
#include "Firestore/core/src/firebase/firestore/util/string_format.h" | ||
#include "Firestore/core/src/firebase/firestore/util/type_traits.h" | ||
#include "absl/meta/type_traits.h" | ||
#include "absl/strings/str_join.h" | ||
|
||
namespace firebase { | ||
namespace firestore { | ||
namespace util { | ||
|
||
template <typename T> | ||
std::string ToString(const T& value); | ||
wilhuff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
namespace impl { | ||
|
||
// Checks whether the given type `T` defines a member function `ToString` | ||
template <typename T, typename = absl::void_t<>> | ||
struct has_to_string : std::false_type {}; | ||
template <typename T> | ||
wilhuff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
struct has_to_string<T, absl::void_t<decltype(std::declval<T>().ToString())>> | ||
: std::true_type {}; | ||
|
||
// Fallback | ||
|
||
template <typename T> | ||
std::string ToStringDefault(const T& value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're mixing "ToString" as a prefix and as a suffix in these member names. Shouldn't these be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
return std::to_string(value); | ||
} | ||
|
||
// Member function `ToString` | ||
|
||
template <typename T> | ||
std::string ToStringCustom(const T& value, std::false_type) { | ||
return ToStringDefault(value); | ||
} | ||
|
||
template <typename T> | ||
std::string ToStringCustom(const T& value, std::true_type) { | ||
return value.ToString(); | ||
} | ||
|
||
// Objective-C class | ||
|
||
template <typename T> | ||
std::string ObjCToString(const T& value, std::false_type) { | ||
return ToStringCustom(value, has_to_string<T>{}); | ||
} | ||
|
||
template <typename T> | ||
std::string ObjCToString(const T& value, std::true_type) { | ||
return MakeString([value description]); | ||
} | ||
|
||
// Container | ||
|
||
template <typename T> | ||
std::string ContainerToString(const T& value, std::false_type) { | ||
return ObjCToString(value, is_objective_c_pointer<T>{}); | ||
} | ||
|
||
template <typename T> | ||
std::string ContainerToString(const T& value, std::true_type) { | ||
std::string contents = absl::StrJoin( | ||
value, ", ", [](std::string* out, const typename T::value_type& element) { | ||
out->append(ToString(element)); | ||
}); | ||
return std::string{"["} + contents + "]"; | ||
} | ||
|
||
// std::string | ||
|
||
template <typename T> | ||
std::string StringToString(const T& value, std::false_type) { | ||
return ContainerToString(value, is_iterable<T>{}); | ||
} | ||
|
||
template <typename T> | ||
std::string StringToString(const T& value, std::true_type) { | ||
return value; | ||
} | ||
|
||
// Associative container | ||
|
||
template <typename T> | ||
std::string MapToString(const T& value, std::false_type) { | ||
return StringToString(value, std::is_convertible<T, std::string>{}); | ||
} | ||
|
||
template <typename T> | ||
std::string MapToString(const T& value, std::true_type) { | ||
std::string contents = absl::StrJoin( | ||
value, ", ", [](std::string* out, const typename T::value_type& kv) { | ||
out->append( | ||
StringFormat("%s: %s", ToString(kv.first), ToString(kv.second))); | ||
}); | ||
return std::string{"{"} + contents + "}"; | ||
} | ||
|
||
} // namespace impl | ||
|
||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Documentation comments are usually better off on the declaration than the definition when they're separate. |
||
* Creates a human-readable description of the given `value`. The representation | ||
* is loosely inspired by Python. | ||
* | ||
* The general idea is to create the description by using the most specific | ||
* available function that creates a string representation of the class; for | ||
* containers, do this recursively, adding some minimal container formatting to | ||
* the output. | ||
* | ||
* Example: | ||
* | ||
* std::vector<DocumentKey> v{ | ||
* DocumentKey({"foo/bar"}), | ||
* DocumentKey({"this/that"}) | ||
* }; | ||
* assert(ToString(v) == "[foo/bar, this/that]"); | ||
wilhuff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
* | ||
* std::map<int, std::string> m{ | ||
{1, "foo"}, | ||
{2, "bar"} | ||
* }; | ||
* assert(ToString(m) == "{1: foo, 2: bar}"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An unordered map might be interesting... (Or at least difficult to test. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done (sort of). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ha. That's a pretty good way to dodge the issue. :) |
||
* | ||
* The following algorithm is used: | ||
* - if `value` is an associative container (`std::map`, `std::unordered_map`, | ||
* `f:f:immutable::SortedMap`, etc.), the description is of the form: | ||
* | ||
* {key1: value1, key2: value2} | ||
* | ||
* where the description of each key and value is created by running | ||
* `ToString` recursively; | ||
* | ||
* - otherwise, if `value` is (convertible to) `std::string`, it's used as is; | ||
* | ||
* - otherwise, if `value` is a container, the description is of the form: | ||
* | ||
* [element1, element2] | ||
* | ||
* where the description of each element is created by running `ToString` | ||
* recursively; | ||
* | ||
* - otherwise, if `value` is an Objective-C class, the description is created | ||
* by calling `[value description]`and converting the result to an | ||
* `std::string`; | ||
* | ||
* - otherwise, if `value` defines a member function called `ToString`, the | ||
* description is created by invoking the function; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If a type is both a (custom) container and defines ToString, then this priority list implies that ToString won't be called. It would seem like ToString() should be given the highest priority rather than the lowest? Regardless of where you end up on the above question, if you care about this ordering, consider adding a test for it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. The ordering is now:
What do you think? Test -- added a simple test. I mostly care about the ordering between associative and simple containers, because it's the only one that will always "conflict", but it's implicitly tested already. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New ordering seems reasonable. And yeah, the ordering between assoc/simple containers is definitely already tested. The one I was referring to, was the ToString + container ordering, which you've now added. ltgm. |
||
* | ||
* - otherwise, `std::to_string` is used as a fallback. If `std::to_string` is | ||
* not defined for the class, a compilation error will be produced. | ||
* | ||
* Implementation notes: to rank different choices and avoid clashes (e.g., | ||
* a type that is an associative container is also a (simple) container), tag | ||
* dispatch is used. Each function in the chain either is tagged by | ||
* `std::true_type` and can process the value, or is tagged by `std::false_type` | ||
* and passes the value to the next function by the rank. When passing to the | ||
* next function, some trait corresponding to the function is given in place of | ||
* the tag; for example, `StringToString`, which can handle `std::string`s, is | ||
* invoked with `std::is_convertible<T, std::string>` as the tag. | ||
*/ | ||
|
||
template <typename T> | ||
std::string ToString(const T& value) { | ||
return impl::MapToString(value, is_associative_container<T>{}); | ||
} | ||
|
||
} // namespace util | ||
} // namespace firestore | ||
} // namespace firebase | ||
|
||
#endif // FIRESTORE_CORE_SRC_FIREBASE_FIRESTORE_UTIL_TO_STRING_H_ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,8 @@ | |
|
||
#include <type_traits> | ||
|
||
#include "absl/meta/type_traits.h" | ||
|
||
namespace firebase { | ||
namespace firestore { | ||
namespace util { | ||
|
@@ -45,43 +47,25 @@ namespace util { | |
* is_objective_c_pointer<std::unique_ptr<int>>::value == false | ||
*/ | ||
template <typename T> | ||
struct is_objective_c_pointer { | ||
private: | ||
using yes_type = char (&)[10]; | ||
using no_type = char (&)[1]; | ||
|
||
/** | ||
* A non-existent function declared to produce a pointer to type T (which is | ||
* consistent with the way Objective-C objects are referenced). | ||
* | ||
* Note that there is no definition for this function but that's okay because | ||
* we only need it to reason about the function's type at compile type. | ||
*/ | ||
static T Pointer(); | ||
|
||
static yes_type Choose(id value); | ||
static no_type Choose(...); | ||
|
||
public: | ||
using value_type = bool; | ||
|
||
enum { value = sizeof(Choose(Pointer())) == sizeof(yes_type) }; | ||
struct is_objective_c_pointer : std::is_convertible<T, id> {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is equivalent to the previous implementation (and the tests pass), but I'll run it by Gil before merging. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If Could this be a using statement? Something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, good idea. |
||
|
||
constexpr operator bool() const { | ||
return value; | ||
} | ||
|
||
constexpr bool operator()() const { | ||
return value; | ||
} | ||
}; | ||
#endif // __OBJC__ | ||
|
||
// Hard-code the answer for `void` because you can't pass arguments of type | ||
// `void` to another function. | ||
template <> | ||
struct is_objective_c_pointer<void> : public std::false_type {}; | ||
template <typename T, typename = absl::void_t<>> | ||
struct is_iterable : std::false_type {}; | ||
template <typename T> | ||
struct is_iterable< | ||
T, | ||
absl::void_t<decltype(std::declval<T>().begin(), std::declval<T>().end())>> | ||
: std::true_type {}; | ||
|
||
#endif // __OBJC__ | ||
template <typename T, typename = absl::void_t<>> | ||
struct is_associative_container : std::false_type {}; | ||
template <typename T> | ||
struct is_associative_container< | ||
T, | ||
absl::void_t<decltype(std::declval<typename T::mapped_type>())>> | ||
: std::true_type {}; | ||
|
||
} // namespace util | ||
} // namespace firestore | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you run
scripts/sync_project.rb
does this change?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.