mirror of
https://github.com/abseil/abseil-cpp.git
synced 2026-06-04 12:07:05 +08:00
Export of internal Abseil changes
-- 34c0d521b11ed4191ea3e071a864a84e5e5941b7 by Matthew Brown <matthewbr@google.com>: Release absl::StrFormat custom type extensions - Allows StrFormat methods to be extended to accept types which implement AbslFormatConvert() - NOLINTNEXTLINE(readability-redundant-declaration) used, declarations are required in some compilers. PiperOrigin-RevId: 316963065 -- 4d475b5ad02d41057447d722ad35573fc4f48d1f by Evan Brown <ezb@google.com>: Small fix to previous change: the first overload of insert_iterator_unique wasn't actually being selected. Fix that issue and add tests to verify that it actually works. Note: couldn't use TestInstanceTracker here because that counts instances (and decrements in destructor) rather than counting all constructor calls. PiperOrigin-RevId: 316927690 GitOrigin-RevId: 34c0d521b11ed4191ea3e071a864a84e5e5941b7 Change-Id: If8bbb8317b93af4084ac4cc55b752b99b1581b58
This commit is contained in:
@@ -2416,6 +2416,41 @@ TEST(Btree, SetRangeConstructorAndInsertSupportExplicitConversionComparable) {
|
||||
EXPECT_THAT(name_set2, ElementsAreArray(names));
|
||||
}
|
||||
|
||||
// A type that is explicitly convertible from int and counts constructor calls.
|
||||
struct ConstructorCounted {
|
||||
explicit ConstructorCounted(int i) : i(i) { ++constructor_calls; }
|
||||
bool operator==(int other) const { return i == other; }
|
||||
|
||||
int i;
|
||||
static int constructor_calls;
|
||||
};
|
||||
int ConstructorCounted::constructor_calls = 0;
|
||||
|
||||
struct ConstructorCountedCompare {
|
||||
bool operator()(int a, const ConstructorCounted &b) const { return a < b.i; }
|
||||
bool operator()(const ConstructorCounted &a, int b) const { return a.i < b; }
|
||||
bool operator()(const ConstructorCounted &a,
|
||||
const ConstructorCounted &b) const {
|
||||
return a.i < b.i;
|
||||
}
|
||||
using is_transparent = void;
|
||||
};
|
||||
|
||||
TEST(Btree,
|
||||
SetRangeConstructorAndInsertExplicitConvComparableLimitConstruction) {
|
||||
const int i[] = {0, 1, 1};
|
||||
ConstructorCounted::constructor_calls = 0;
|
||||
|
||||
absl::btree_set<ConstructorCounted, ConstructorCountedCompare> set{
|
||||
std::begin(i), std::end(i)};
|
||||
EXPECT_THAT(set, ElementsAre(0, 1));
|
||||
EXPECT_EQ(ConstructorCounted::constructor_calls, 2);
|
||||
|
||||
set.insert(std::begin(i), std::end(i));
|
||||
EXPECT_THAT(set, ElementsAre(0, 1));
|
||||
EXPECT_EQ(ConstructorCounted::constructor_calls, 2);
|
||||
}
|
||||
|
||||
TEST(Btree,
|
||||
SetRangeConstructorAndInsertSupportExplicitConversionNonComparable) {
|
||||
const int i[] = {0, 1};
|
||||
@@ -2443,6 +2478,21 @@ TEST(Btree, MapRangeConstructorAndInsertSupportExplicitConversionComparable) {
|
||||
EXPECT_THAT(name_map2, ElementsAre(Pair("n1", 1), Pair("n2", 2)));
|
||||
}
|
||||
|
||||
TEST(Btree,
|
||||
MapRangeConstructorAndInsertExplicitConvComparableLimitConstruction) {
|
||||
const std::pair<int, int> i[] = {{0, 1}, {1, 2}, {1, 3}};
|
||||
ConstructorCounted::constructor_calls = 0;
|
||||
|
||||
absl::btree_map<ConstructorCounted, int, ConstructorCountedCompare> map{
|
||||
std::begin(i), std::end(i)};
|
||||
EXPECT_THAT(map, ElementsAre(Pair(0, 1), Pair(1, 2)));
|
||||
EXPECT_EQ(ConstructorCounted::constructor_calls, 2);
|
||||
|
||||
map.insert(std::begin(i), std::end(i));
|
||||
EXPECT_THAT(map, ElementsAre(Pair(0, 1), Pair(1, 2)));
|
||||
EXPECT_EQ(ConstructorCounted::constructor_calls, 2);
|
||||
}
|
||||
|
||||
TEST(Btree,
|
||||
MapRangeConstructorAndInsertSupportExplicitConversionNonComparable) {
|
||||
const std::pair<int, int> i[] = {{0, 1}, {1, 2}};
|
||||
|
||||
@@ -1208,9 +1208,9 @@ class btree {
|
||||
// Note: the first overload avoids constructing a value_type if the key
|
||||
// already exists in the btree.
|
||||
template <typename InputIterator,
|
||||
typename = decltype(
|
||||
compare_keys(params_type::key(*std::declval<InputIterator>()),
|
||||
std::declval<const key_type &>()))>
|
||||
typename = decltype(std::declval<const key_compare &>()(
|
||||
params_type::key(*std::declval<InputIterator>()),
|
||||
std::declval<const key_type &>()))>
|
||||
void insert_iterator_unique(InputIterator b, InputIterator e, int);
|
||||
// We need the second overload for cases in which we need to construct a
|
||||
// value_type in order to compare it with the keys already in the btree.
|
||||
|
||||
@@ -25,10 +25,12 @@ class Cord;
|
||||
class FormatCountCapture;
|
||||
class FormatSink;
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
template <absl::FormatConversionCharSet C>
|
||||
struct FormatConvertResult;
|
||||
class FormatConversionSpec;
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
template <typename T, typename = void>
|
||||
struct HasUserDefinedConvert : std::false_type {};
|
||||
|
||||
@@ -39,6 +41,22 @@ struct HasUserDefinedConvert<T, void_t<decltype(AbslFormatConvert(
|
||||
std::declval<FormatSink*>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
void AbslFormatConvert(); // Stops the lexical name lookup
|
||||
template <typename T>
|
||||
auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv,
|
||||
FormatSinkImpl* sink)
|
||||
-> decltype(AbslFormatConvert(v,
|
||||
std::declval<const FormatConversionSpec&>(),
|
||||
std::declval<FormatSink*>())) {
|
||||
using FormatConversionSpecT =
|
||||
absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatConversionSpec>;
|
||||
using FormatSinkT =
|
||||
absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>;
|
||||
auto fcs = conv.Wrap<FormatConversionSpecT>();
|
||||
auto fs = sink->Wrap<FormatSinkT>();
|
||||
return AbslFormatConvert(v, fcs, &fs);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class StreamedWrapper;
|
||||
|
||||
@@ -46,6 +64,13 @@ class StreamedWrapper;
|
||||
// then convert it, appending to `sink` and return `true`.
|
||||
// Otherwise fail and return `false`.
|
||||
|
||||
// AbslFormatConvert(v, conv, sink) is intended to be found by ADL on 'v'
|
||||
// as an extension mechanism. These FormatConvertImpl functions are the default
|
||||
// implementations.
|
||||
// The ADL search is augmented via the 'Sink*' parameter, which also
|
||||
// serves as a disambiguator to reject possible unintended 'AbslFormatConvert'
|
||||
// functions in the namespaces associated with 'v'.
|
||||
|
||||
// Raw pointers.
|
||||
struct VoidPtr {
|
||||
VoidPtr() = default;
|
||||
@@ -61,6 +86,11 @@ struct ArgConvertResult {
|
||||
bool value;
|
||||
};
|
||||
|
||||
template <FormatConversionCharSet C>
|
||||
constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) {
|
||||
return C;
|
||||
}
|
||||
|
||||
template <FormatConversionCharSet C>
|
||||
constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) {
|
||||
return C;
|
||||
|
||||
@@ -23,8 +23,17 @@ class FormatArgImplTest : public ::testing::Test {
|
||||
enum Color { kRed, kGreen, kBlue };
|
||||
|
||||
static const char *hi() { return "hi"; }
|
||||
|
||||
struct X {};
|
||||
|
||||
X x_;
|
||||
};
|
||||
|
||||
inline FormatConvertResult<FormatConversionCharSet{}> AbslFormatConvert(
|
||||
const FormatArgImplTest::X &, const FormatConversionSpec &, FormatSink *) {
|
||||
return {false};
|
||||
}
|
||||
|
||||
TEST_F(FormatArgImplTest, ToInt) {
|
||||
int out = 0;
|
||||
EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(1), &out));
|
||||
@@ -59,6 +68,7 @@ TEST_F(FormatArgImplTest, ToInt) {
|
||||
FormatArgImpl(static_cast<int *>(nullptr)), &out));
|
||||
EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(hi()), &out));
|
||||
EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl("hi"), &out));
|
||||
EXPECT_FALSE(FormatArgImplFriend::ToInt(FormatArgImpl(x_), &out));
|
||||
EXPECT_TRUE(FormatArgImplFriend::ToInt(FormatArgImpl(kBlue), &out));
|
||||
EXPECT_EQ(2, out);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,29 @@ std::string Flags::ToString() const {
|
||||
return s;
|
||||
}
|
||||
|
||||
#define ABSL_INTERNAL_X_VAL(id) \
|
||||
constexpr absl::FormatConversionChar FormatConversionCharInternal::id;
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_X_VAL, )
|
||||
#undef ABSL_INTERNAL_X_VAL
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr absl::FormatConversionChar FormatConversionCharInternal::kNone;
|
||||
|
||||
#define ABSL_INTERNAL_CHAR_SET_CASE(c) \
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::c;
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(ABSL_INTERNAL_CHAR_SET_CASE, )
|
||||
#undef ABSL_INTERNAL_CHAR_SET_CASE
|
||||
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kStar;
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kIntegral;
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kFloating;
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kNumeric;
|
||||
// NOLINTNEXTLINE(readability-redundant-declaration)
|
||||
constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer;
|
||||
|
||||
bool FormatSinkImpl::PutPaddedString(string_view value, int width,
|
||||
int precision, bool left) {
|
||||
size_t space_remaining = 0;
|
||||
|
||||
@@ -31,11 +31,11 @@
|
||||
namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
enum class FormatConversionChar : uint8_t;
|
||||
enum class FormatConversionCharSet : uint64_t;
|
||||
|
||||
namespace str_format_internal {
|
||||
|
||||
class FormatRawSinkImpl {
|
||||
public:
|
||||
// Implicitly convert from any type that provides the hook function as
|
||||
@@ -361,14 +361,12 @@ struct FormatConversionCharSetInternal {
|
||||
static constexpr FormatConversionCharSet kStar =
|
||||
FormatConversionCharToConvValue('*');
|
||||
|
||||
// Some predefined values (TODO(matthewbr), delete any that are unused).
|
||||
static constexpr FormatConversionCharSet kIntegral =
|
||||
FormatConversionCharSetUnion(d, i, u, o, x, X);
|
||||
static constexpr FormatConversionCharSet kFloating =
|
||||
FormatConversionCharSetUnion(a, e, f, g, A, E, F, G);
|
||||
static constexpr FormatConversionCharSet kNumeric =
|
||||
FormatConversionCharSetUnion(kIntegral, kFloating);
|
||||
static constexpr FormatConversionCharSet kString = s;
|
||||
static constexpr FormatConversionCharSet kPointer = p;
|
||||
};
|
||||
|
||||
|
||||
@@ -80,4 +80,19 @@ TEST(FormatExtensionTest, SinkAppendChars) {
|
||||
EXPECT_EQ(actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FormatExtensionTest, VerifyEnumEquality) {
|
||||
#define X_VAL(id) \
|
||||
EXPECT_EQ(absl::FormatConversionChar::id, \
|
||||
absl::str_format_internal::FormatConversionCharInternal::id);
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, );
|
||||
#undef X_VAL
|
||||
|
||||
#define X_VAL(id) \
|
||||
EXPECT_EQ(absl::FormatConversionCharSet::id, \
|
||||
absl::str_format_internal::FormatConversionCharSetInternal::id);
|
||||
ABSL_INTERNAL_CONVERSION_CHARS_EXPAND_(X_VAL, );
|
||||
#undef X_VAL
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -63,6 +63,9 @@
|
||||
// loosely typed. `FormatUntyped()` is not a template and does not perform
|
||||
// any compile-time checking of the format string; instead, it returns a
|
||||
// boolean from a runtime check.
|
||||
//
|
||||
// In addition, the `str_format` library provides extension points for
|
||||
// augmenting formatting to new types. See "StrFormat Extensions" below.
|
||||
|
||||
#ifndef ABSL_STRINGS_STR_FORMAT_H_
|
||||
#define ABSL_STRINGS_STR_FORMAT_H_
|
||||
@@ -278,9 +281,36 @@ using FormatSpec = str_format_internal::FormatSpecTemplate<
|
||||
// } else {
|
||||
// ... error case ...
|
||||
// }
|
||||
|
||||
#if defined(__cpp_nontype_template_parameter_auto)
|
||||
// If C++17 is available, an 'extended' format is also allowed that can specify
|
||||
// multiple conversion characters per format argument, using a combination of
|
||||
// `absl::FormatConversionCharSet` enum values (logically a set union)
|
||||
// via the `|` operator. (Single character-based arguments are still accepted,
|
||||
// but cannot be combined). Some common conversions also have predefined enum
|
||||
// values, such as `absl::FormatConversionCharSet::kIntegral`.
|
||||
//
|
||||
// Example:
|
||||
// // Extended format supports multiple conversion characters per argument,
|
||||
// // specified via a combination of `FormatConversionCharSet` enums.
|
||||
// using MyFormat = absl::ParsedFormat<absl::FormatConversionCharSet::d |
|
||||
// absl::FormatConversionCharSet::x>;
|
||||
// MyFormat GetFormat(bool use_hex) {
|
||||
// if (use_hex) return MyFormat("foo %x bar");
|
||||
// return MyFormat("foo %d bar");
|
||||
// }
|
||||
// // `format` can be used with any value that supports 'd' and 'x',
|
||||
// // like `int`.
|
||||
// auto format = GetFormat(use_hex);
|
||||
// value = StringF(format, i);
|
||||
template <auto... Conv>
|
||||
using ParsedFormat = absl::str_format_internal::ExtendedParsedFormat<
|
||||
absl::str_format_internal::ToFormatConversionCharSet(Conv)...>;
|
||||
#else
|
||||
template <char... Conv>
|
||||
using ParsedFormat = str_format_internal::ExtendedParsedFormat<
|
||||
absl::str_format_internal::ToFormatConversionCharSet(Conv)...>;
|
||||
#endif // defined(__cpp_nontype_template_parameter_auto)
|
||||
|
||||
// StrFormat()
|
||||
//
|
||||
@@ -537,6 +567,246 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped(
|
||||
str_format_internal::UntypedFormatSpecImpl::Extract(format), args);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// StrFormat Extensions
|
||||
//------------------------------------------------------------------------------
|
||||
//
|
||||
// AbslFormatConvert()
|
||||
//
|
||||
// The StrFormat library provides a customization API for formatting
|
||||
// user-defined types using absl::StrFormat(). The API relies on detecting an
|
||||
// overload in the user-defined type's namespace of a free (non-member)
|
||||
// `AbslFormatConvert()` function, usually as a friend definition with the
|
||||
// following signature:
|
||||
//
|
||||
// absl::FormatConvertResult<...> AbslFormatConvert(
|
||||
// const X& value,
|
||||
// const absl::FormatConversionSpec& spec,
|
||||
// absl::FormatSink *sink);
|
||||
//
|
||||
// An `AbslFormatConvert()` overload for a type should only be declared in the
|
||||
// same file and namespace as said type.
|
||||
//
|
||||
// The abstractions within this definition include:
|
||||
//
|
||||
// * An `absl::FormatConversionSpec` to specify the fields to pull from a
|
||||
// user-defined type's format string
|
||||
// * An `absl::FormatSink` to hold the converted string data during the
|
||||
// conversion process.
|
||||
// * An `absl::FormatConvertResult` to hold the status of the returned
|
||||
// formatting operation
|
||||
//
|
||||
// The return type encodes all the conversion characters that your
|
||||
// AbslFormatConvert() routine accepts. The return value should be {true}.
|
||||
// A return value of {false} will result in `StrFormat()` returning
|
||||
// an empty string. This result will be propagated to the result of
|
||||
// `FormatUntyped`.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// struct Point {
|
||||
// // To add formatting support to `Point`, we simply need to add a free
|
||||
// // (non-member) function `AbslFormatConvert()`. This method interprets
|
||||
// // `spec` to print in the request format. The allowed conversion characters
|
||||
// // can be restricted via the type of the result, in this example
|
||||
// // string and integral formatting are allowed (but not, for instance
|
||||
// // floating point characters like "%f"). You can add such a free function
|
||||
// // using a friend declaration within the body of the class:
|
||||
// friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
|
||||
// absl::FormatConversionCharSet::kIntegral>
|
||||
// AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
|
||||
// absl::FormatSink* s) {
|
||||
// if (spec.conversion_char() == absl::FormatConversionChar::s) {
|
||||
// s->Append(absl::StrCat("x=", p.x, " y=", p.y));
|
||||
// } else {
|
||||
// s->Append(absl::StrCat(p.x, ",", p.y));
|
||||
// }
|
||||
// return {true};
|
||||
// }
|
||||
//
|
||||
// int x;
|
||||
// int y;
|
||||
// };
|
||||
|
||||
// clang-format off
|
||||
|
||||
// FormatConversionChar
|
||||
//
|
||||
// Specifies the formatting character provided in the format string
|
||||
// passed to `StrFormat()`.
|
||||
enum class FormatConversionChar : uint8_t {
|
||||
c, s, // text
|
||||
d, i, o, u, x, X, // int
|
||||
f, F, e, E, g, G, a, A, // float
|
||||
n, p // misc
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// FormatConversionSpec
|
||||
//
|
||||
// Specifies modifications to the conversion of the format string, through use
|
||||
// of one or more format flags in the source format string.
|
||||
class FormatConversionSpec {
|
||||
public:
|
||||
// FormatConversionSpec::is_basic()
|
||||
//
|
||||
// Indicates that width and precision are not specified, and no additional
|
||||
// flags are set for this conversion character in the format string.
|
||||
bool is_basic() const { return impl_.is_basic(); }
|
||||
|
||||
// FormatConversionSpec::has_left_flag()
|
||||
//
|
||||
// Indicates whether the result should be left justified for this conversion
|
||||
// character in the format string. This flag is set through use of a '-'
|
||||
// character in the format string. E.g. "%-s"
|
||||
bool has_left_flag() const { return impl_.has_left_flag(); }
|
||||
|
||||
// FormatConversionSpec::has_show_pos_flag()
|
||||
//
|
||||
// Indicates whether a sign column is prepended to the result for this
|
||||
// conversion character in the format string, even if the result is positive.
|
||||
// This flag is set through use of a '+' character in the format string.
|
||||
// E.g. "%+d"
|
||||
bool has_show_pos_flag() const { return impl_.has_show_pos_flag(); }
|
||||
|
||||
// FormatConversionSpec::has_sign_col_flag()
|
||||
//
|
||||
// Indicates whether a mandatory sign column is added to the result for this
|
||||
// conversion character. This flag is set through use of a space character
|
||||
// (' ') in the format string. E.g. "% i"
|
||||
bool has_sign_col_flag() const { return impl_.has_sign_col_flag(); }
|
||||
|
||||
// FormatConversionSpec::has_alt_flag()
|
||||
//
|
||||
// Indicates whether an "alternate" format is applied to the result for this
|
||||
// conversion character. Alternative forms depend on the type of conversion
|
||||
// character, and unallowed alternatives are undefined. This flag is set
|
||||
// through use of a '#' character in the format string. E.g. "%#h"
|
||||
bool has_alt_flag() const { return impl_.has_alt_flag(); }
|
||||
|
||||
// FormatConversionSpec::has_zero_flag()
|
||||
//
|
||||
// Indicates whether zeroes should be prepended to the result for this
|
||||
// conversion character instead of spaces. This flag is set through use of the
|
||||
// '0' character in the format string. E.g. "%0f"
|
||||
bool has_zero_flag() const { return impl_.has_zero_flag(); }
|
||||
|
||||
// FormatConversionSpec::conversion_char()
|
||||
//
|
||||
// Returns the underlying conversion character.
|
||||
FormatConversionChar conversion_char() const {
|
||||
return impl_.conversion_char();
|
||||
}
|
||||
|
||||
// FormatConversionSpec::width()
|
||||
//
|
||||
// Returns the specified width (indicated through use of a non-zero integer
|
||||
// value or '*' character) of the conversion character. If width is
|
||||
// unspecified, it returns a negative value.
|
||||
int width() const { return impl_.width(); }
|
||||
|
||||
// FormatConversionSpec::precision()
|
||||
//
|
||||
// Returns the specified precision (through use of the '.' character followed
|
||||
// by a non-zero integer value or '*' character) of the conversion character.
|
||||
// If precision is unspecified, it returns a negative value.
|
||||
int precision() const { return impl_.precision(); }
|
||||
|
||||
private:
|
||||
explicit FormatConversionSpec(
|
||||
str_format_internal::FormatConversionSpecImpl impl)
|
||||
: impl_(impl) {}
|
||||
|
||||
friend str_format_internal::FormatConversionSpecImpl;
|
||||
|
||||
absl::str_format_internal::FormatConversionSpecImpl impl_;
|
||||
};
|
||||
|
||||
// Type safe OR operator for FormatConversionCharSet to allow accepting multiple
|
||||
// conversion chars in custom format converters.
|
||||
constexpr FormatConversionCharSet operator|(FormatConversionCharSet a,
|
||||
FormatConversionCharSet b) {
|
||||
return static_cast<FormatConversionCharSet>(static_cast<uint64_t>(a) |
|
||||
static_cast<uint64_t>(b));
|
||||
}
|
||||
|
||||
// FormatConversionCharSet
|
||||
//
|
||||
// Specifies the _accepted_ conversion types as a template parameter to
|
||||
// FormatConvertResult for custom implementations of `AbslFormatConvert`.
|
||||
// Note the helper predefined alias definitions (kIntegral, etc.) below.
|
||||
enum class FormatConversionCharSet : uint64_t {
|
||||
// text
|
||||
c = str_format_internal::FormatConversionCharToConvInt('c'),
|
||||
s = str_format_internal::FormatConversionCharToConvInt('s'),
|
||||
// integer
|
||||
d = str_format_internal::FormatConversionCharToConvInt('d'),
|
||||
i = str_format_internal::FormatConversionCharToConvInt('i'),
|
||||
o = str_format_internal::FormatConversionCharToConvInt('o'),
|
||||
u = str_format_internal::FormatConversionCharToConvInt('u'),
|
||||
x = str_format_internal::FormatConversionCharToConvInt('x'),
|
||||
X = str_format_internal::FormatConversionCharToConvInt('X'),
|
||||
// Float
|
||||
f = str_format_internal::FormatConversionCharToConvInt('f'),
|
||||
F = str_format_internal::FormatConversionCharToConvInt('F'),
|
||||
e = str_format_internal::FormatConversionCharToConvInt('e'),
|
||||
E = str_format_internal::FormatConversionCharToConvInt('E'),
|
||||
g = str_format_internal::FormatConversionCharToConvInt('g'),
|
||||
G = str_format_internal::FormatConversionCharToConvInt('G'),
|
||||
a = str_format_internal::FormatConversionCharToConvInt('a'),
|
||||
A = str_format_internal::FormatConversionCharToConvInt('A'),
|
||||
// misc
|
||||
n = str_format_internal::FormatConversionCharToConvInt('n'),
|
||||
p = str_format_internal::FormatConversionCharToConvInt('p'),
|
||||
|
||||
// Used for width/precision '*' specification.
|
||||
kStar = static_cast<uint64_t>(
|
||||
absl::str_format_internal::FormatConversionCharSetInternal::kStar),
|
||||
// Some predefined values:
|
||||
kIntegral = d | i | u | o | x | X,
|
||||
kFloating = a | e | f | g | A | E | F | G,
|
||||
kNumeric = kIntegral | kFloating,
|
||||
kString = s,
|
||||
kPointer = p,
|
||||
};
|
||||
|
||||
// FormatSink
|
||||
//
|
||||
// An abstraction to which conversions write their string data.
|
||||
//
|
||||
class FormatSink {
|
||||
public:
|
||||
// Appends `count` copies of `ch`.
|
||||
void Append(size_t count, char ch) { sink_->Append(count, ch); }
|
||||
|
||||
void Append(string_view v) { sink_->Append(v); }
|
||||
|
||||
// Appends the first `precision` bytes of `v`. If this is less than
|
||||
// `width`, spaces will be appended first (if `left` is false), or
|
||||
// after (if `left` is true) to ensure the total amount appended is
|
||||
// at least `width`.
|
||||
bool PutPaddedString(string_view v, int width, int precision, bool left) {
|
||||
return sink_->PutPaddedString(v, width, precision, left);
|
||||
}
|
||||
|
||||
private:
|
||||
friend str_format_internal::FormatSinkImpl;
|
||||
explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {}
|
||||
str_format_internal::FormatSinkImpl* sink_;
|
||||
};
|
||||
|
||||
// FormatConvertResult
|
||||
//
|
||||
// Indicates whether a call to AbslFormatConvert() was successful.
|
||||
// This return type informs the StrFormat extension framework (through
|
||||
// ADL but using the return type) of what conversion characters are supported.
|
||||
// It is strongly discouraged to return {false}, as this will result in an
|
||||
// empty string in StrFormat.
|
||||
template <FormatConversionCharSet C>
|
||||
struct FormatConvertResult {
|
||||
bool value;
|
||||
};
|
||||
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace absl {
|
||||
ABSL_NAMESPACE_BEGIN
|
||||
namespace {
|
||||
using str_format_internal::FormatArgImpl;
|
||||
using str_format_internal::FormatConversionCharSetInternal;
|
||||
|
||||
using FormatEntryPointTest = ::testing::Test;
|
||||
|
||||
@@ -537,46 +536,90 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) {
|
||||
EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format)));
|
||||
}
|
||||
|
||||
using absl::str_format_internal::FormatConversionCharSet;
|
||||
#if defined(__cpp_nontype_template_parameter_auto)
|
||||
|
||||
template <auto T>
|
||||
std::true_type IsValidParsedFormatArgTest(ParsedFormat<T>*);
|
||||
|
||||
template <auto T>
|
||||
std::false_type IsValidParsedFormatArgTest(...);
|
||||
|
||||
template <auto T>
|
||||
using IsValidParsedFormatArg = decltype(IsValidParsedFormatArgTest<T>(nullptr));
|
||||
|
||||
TEST_F(ParsedFormatTest, OnlyValidTypesAllowed) {
|
||||
ASSERT_TRUE(IsValidParsedFormatArg<'c'>::value);
|
||||
|
||||
ASSERT_TRUE(IsValidParsedFormatArg<FormatConversionCharSet::d>::value);
|
||||
|
||||
ASSERT_TRUE(IsValidParsedFormatArg<absl::FormatConversionCharSet::d |
|
||||
absl::FormatConversionCharSet::x>::value);
|
||||
ASSERT_TRUE(
|
||||
IsValidParsedFormatArg<absl::FormatConversionCharSet::kIntegral>::value);
|
||||
|
||||
// This is an easy mistake to make, however, this will reduce to an integer
|
||||
// which has no meaning, so we need to ensure it doesn't compile.
|
||||
ASSERT_FALSE(IsValidParsedFormatArg<'x' | 'd'>::value);
|
||||
|
||||
// For now, we disallow construction based on ConversionChar (rather than
|
||||
// CharSet)
|
||||
ASSERT_FALSE(IsValidParsedFormatArg<absl::FormatConversionChar::d>::value);
|
||||
}
|
||||
|
||||
TEST_F(ParsedFormatTest, ExtendedTyping) {
|
||||
EXPECT_FALSE(ParsedFormat<FormatConversionCharSet::d>::New(""));
|
||||
ASSERT_TRUE(ParsedFormat<absl::FormatConversionCharSet::d>::New("%d"));
|
||||
auto v1 = ParsedFormat<'d', absl::FormatConversionCharSet::s>::New("%d%s");
|
||||
ASSERT_TRUE(v1);
|
||||
auto v2 = ParsedFormat<absl::FormatConversionCharSet::d, 's'>::New("%d%s");
|
||||
ASSERT_TRUE(v2);
|
||||
auto v3 = ParsedFormat<absl::FormatConversionCharSet::d |
|
||||
absl::FormatConversionCharSet::s,
|
||||
's'>::New("%d%s");
|
||||
ASSERT_TRUE(v3);
|
||||
auto v4 = ParsedFormat<absl::FormatConversionCharSet::d |
|
||||
absl::FormatConversionCharSet::s,
|
||||
's'>::New("%s%s");
|
||||
ASSERT_TRUE(v4);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedCorrect) {
|
||||
auto f =
|
||||
ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New("ABC%dDEF");
|
||||
ExtendedParsedFormat<absl::FormatConversionCharSet::d>::New("ABC%dDEF");
|
||||
ASSERT_TRUE(f);
|
||||
EXPECT_EQ("[ABC]{d:1$d}[DEF]", SummarizeParsedFormat(*f));
|
||||
|
||||
std::string format = "%sFFF%dZZZ%f";
|
||||
auto f2 = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::kString,
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::kFloating>::New(format);
|
||||
absl::FormatConversionCharSet::kString, absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::kFloating>::New(format);
|
||||
|
||||
ASSERT_TRUE(f2);
|
||||
EXPECT_EQ("{s:1$s}[FFF]{d:2$d}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2));
|
||||
|
||||
f2 = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::kString,
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::kFloating>::New("%s %d %f");
|
||||
absl::FormatConversionCharSet::kString, absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::kFloating>::New("%s %d %f");
|
||||
|
||||
ASSERT_TRUE(f2);
|
||||
EXPECT_EQ("{s:1$s}[ ]{d:2$d}[ ]{f:3$f}", SummarizeParsedFormat(*f2));
|
||||
|
||||
auto star =
|
||||
ExtendedParsedFormat<FormatConversionCharSetInternal::kStar,
|
||||
FormatConversionCharSetInternal::d>::New("%*d");
|
||||
ExtendedParsedFormat<absl::FormatConversionCharSet::kStar,
|
||||
absl::FormatConversionCharSet::d>::New("%*d");
|
||||
ASSERT_TRUE(star);
|
||||
EXPECT_EQ("{*d:2$1$*d}", SummarizeParsedFormat(*star));
|
||||
|
||||
auto dollar = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::New("%2$s %1$d");
|
||||
auto dollar =
|
||||
ExtendedParsedFormat<absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::New("%2$s %1$d");
|
||||
ASSERT_TRUE(dollar);
|
||||
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}", SummarizeParsedFormat(*dollar));
|
||||
// with reuse
|
||||
dollar = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::New("%2$s %1$d %1$d");
|
||||
absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::New("%2$s %1$d %1$d");
|
||||
ASSERT_TRUE(dollar);
|
||||
EXPECT_EQ("{2$s:2$s}[ ]{1$d:1$d}[ ]{1$d:1$d}",
|
||||
SummarizeParsedFormat(*dollar));
|
||||
@@ -584,62 +627,61 @@ TEST_F(ParsedFormatTest, UncheckedCorrect) {
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) {
|
||||
EXPECT_FALSE(
|
||||
(ExtendedParsedFormat<FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::New("ABC")));
|
||||
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::New("ABC")));
|
||||
EXPECT_FALSE(
|
||||
(ExtendedParsedFormat<FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::New("%dABC")));
|
||||
EXPECT_FALSE((ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::New("ABC%2$s")));
|
||||
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::New("%dABC")));
|
||||
EXPECT_FALSE(
|
||||
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::New("ABC%2$s")));
|
||||
auto f = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::NewAllowIgnored("ABC");
|
||||
absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::NewAllowIgnored("ABC");
|
||||
ASSERT_TRUE(f);
|
||||
EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f));
|
||||
f = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::NewAllowIgnored("%dABC");
|
||||
absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::NewAllowIgnored("%dABC");
|
||||
ASSERT_TRUE(f);
|
||||
EXPECT_EQ("{d:1$d}[ABC]", SummarizeParsedFormat(*f));
|
||||
f = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::s>::NewAllowIgnored("ABC%2$s");
|
||||
absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::s>::NewAllowIgnored("ABC%2$s");
|
||||
ASSERT_TRUE(f);
|
||||
EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f));
|
||||
}
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedMultipleTypes) {
|
||||
auto dx = ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d |
|
||||
FormatConversionCharSetInternal::x>::New("%1$d %1$x");
|
||||
auto dx =
|
||||
ExtendedParsedFormat<absl::FormatConversionCharSet::d |
|
||||
absl::FormatConversionCharSet::x>::New("%1$d %1$x");
|
||||
EXPECT_TRUE(dx);
|
||||
EXPECT_EQ("{1$d:1$d}[ ]{1$x:1$x}", SummarizeParsedFormat(*dx));
|
||||
|
||||
dx = ExtendedParsedFormat<FormatConversionCharSetInternal::d |
|
||||
FormatConversionCharSetInternal::x>::New("%1$d");
|
||||
dx = ExtendedParsedFormat<absl::FormatConversionCharSet::d |
|
||||
absl::FormatConversionCharSet::x>::New("%1$d");
|
||||
EXPECT_TRUE(dx);
|
||||
EXPECT_EQ("{1$d:1$d}", SummarizeParsedFormat(*dx));
|
||||
}
|
||||
|
||||
TEST_F(ParsedFormatTest, UncheckedIncorrect) {
|
||||
EXPECT_FALSE(
|
||||
ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New(""));
|
||||
EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::d>::New(""));
|
||||
|
||||
EXPECT_FALSE(ExtendedParsedFormat<FormatConversionCharSetInternal::d>::New(
|
||||
EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::d>::New(
|
||||
"ABC%dDEF%d"));
|
||||
|
||||
std::string format = "%sFFF%dZZZ%f";
|
||||
EXPECT_FALSE(
|
||||
(ExtendedParsedFormat<FormatConversionCharSetInternal::s,
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::g>::New(format)));
|
||||
(ExtendedParsedFormat<absl::FormatConversionCharSet::s,
|
||||
absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::g>::New(format)));
|
||||
}
|
||||
|
||||
TEST_F(ParsedFormatTest, RegressionMixPositional) {
|
||||
EXPECT_FALSE((ExtendedParsedFormat<
|
||||
FormatConversionCharSetInternal::d,
|
||||
FormatConversionCharSetInternal::o>::New("%1$d %o")));
|
||||
EXPECT_FALSE(
|
||||
(ExtendedParsedFormat<absl::FormatConversionCharSet::d,
|
||||
absl::FormatConversionCharSet::o>::New("%1$d %o")));
|
||||
}
|
||||
|
||||
using FormatWrapperTest = ::testing::Test;
|
||||
@@ -664,6 +706,38 @@ TEST_F(FormatWrapperTest, ParsedFormat) {
|
||||
ABSL_NAMESPACE_END
|
||||
} // namespace absl
|
||||
|
||||
using FormatExtensionTest = ::testing::Test;
|
||||
|
||||
struct Point {
|
||||
friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString |
|
||||
absl::FormatConversionCharSet::kIntegral>
|
||||
AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec,
|
||||
absl::FormatSink* s) {
|
||||
if (spec.conversion_char() == absl::FormatConversionChar::s) {
|
||||
s->Append(absl::StrCat("x=", p.x, " y=", p.y));
|
||||
} else {
|
||||
s->Append(absl::StrCat(p.x, ",", p.y));
|
||||
}
|
||||
return {true};
|
||||
}
|
||||
|
||||
int x = 10;
|
||||
int y = 20;
|
||||
};
|
||||
|
||||
TEST_F(FormatExtensionTest, AbslFormatConvertExample) {
|
||||
Point p;
|
||||
EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z");
|
||||
EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z");
|
||||
|
||||
// Typed formatting will fail to compile an invalid format.
|
||||
// StrFormat("%f", p); // Does not compile.
|
||||
std::string actual;
|
||||
absl::UntypedFormatSpec f1("%f");
|
||||
// FormatUntyped will return false for bad character.
|
||||
EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)}));
|
||||
}
|
||||
|
||||
// Some codegen thunks that we can use to easily dump the generated assembly for
|
||||
// different StrFormat calls.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user