aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorAlexander Smirnov <alex@ydb.tech>2024-06-18 08:31:45 +0000
committerAlexander Smirnov <alex@ydb.tech>2024-06-18 08:31:45 +0000
commit8da3a80eb37bb357a4a994464851104565fd9490 (patch)
tree021aa0770c7bb74d27d8731629ce6f5abd1e84e9 /library/cpp
parent05cc35d98bf2bd929c00b02fcd98fd65a1b676ad (diff)
parentd68a678bf36d213cb890d8bdfbdf34df6a40f8c1 (diff)
downloadydb-8da3a80eb37bb357a4a994464851104565fd9490.tar.gz
Merge branch 'rightlib' into mergelibs-240618-0830
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/case_insensitive_string/ut_gtest/case_insensitive_string_compare.cpp19
-rw-r--r--library/cpp/protobuf/json/config.h11
-rw-r--r--library/cpp/protobuf/json/proto2json_printer.cpp40
-rw-r--r--library/cpp/protobuf/json/proto2json_printer.h3
-rw-r--r--library/cpp/protobuf/json/ut/proto2json_ut.cpp193
-rw-r--r--library/cpp/tld/tlds-alpha-by-domain.txt3
-rw-r--r--library/cpp/yt/logging/logger-inl.h55
-rw-r--r--library/cpp/yt/logging/logger.h15
-rw-r--r--library/cpp/yt/logging/static_analysis-inl.h146
-rw-r--r--library/cpp/yt/logging/static_analysis.h22
-rw-r--r--library/cpp/yt/logging/unittests/static_analysis_ut.cpp40
-rw-r--r--library/cpp/yt/misc/optional.h6
-rw-r--r--library/cpp/yt/misc/source_location.cpp26
-rw-r--r--library/cpp/yt/misc/source_location.h17
-rw-r--r--library/cpp/yt/misc/strong_typedef.h7
-rw-r--r--library/cpp/yt/misc/variant-inl.h6
-rw-r--r--library/cpp/yt/misc/variant.cpp2
-rw-r--r--library/cpp/yt/misc/variant.h5
-rw-r--r--library/cpp/yt/string/format-inl.h86
-rw-r--r--library/cpp/yt/string/format.h21
-rw-r--r--library/cpp/yt/string/format_analyser-inl.h12
-rw-r--r--library/cpp/yt/string/format_arg-inl.h7
-rw-r--r--library/cpp/yt/string/format_arg.h11
-rw-r--r--library/cpp/yt/string/format_string-inl.h26
-rw-r--r--library/cpp/yt/string/format_string.cpp18
-rw-r--r--library/cpp/yt/string/format_string.h34
-rw-r--r--library/cpp/yt/string/readme.md314
-rw-r--r--library/cpp/yt/string/string_builder-inl.h9
-rw-r--r--library/cpp/yt/string/string_builder.h4
-rw-r--r--library/cpp/yt/string/unittests/format_ut.cpp6
-rw-r--r--library/cpp/yt/string/ya.make1
-rw-r--r--library/cpp/yt/threading/fork_aware_spin_lock.h4
32 files changed, 785 insertions, 384 deletions
diff --git a/library/cpp/case_insensitive_string/ut_gtest/case_insensitive_string_compare.cpp b/library/cpp/case_insensitive_string/ut_gtest/case_insensitive_string_compare.cpp
index fa37e075b8a..cecd6f50443 100644
--- a/library/cpp/case_insensitive_string/ut_gtest/case_insensitive_string_compare.cpp
+++ b/library/cpp/case_insensitive_string/ut_gtest/case_insensitive_string_compare.cpp
@@ -54,21 +54,24 @@ TEST(CaseInsensitiveAsciiString, CompareAsciiWithoutNullBytes) {
}
}
-TEST(CaseInsensitiveAsciiString, MaySupportLocales) {
+TEST(CaseInsensitiveAsciiString, UnspecifiedBehaviorForNonAsciiStrings) {
TLocaleGuard loc("ru_RU.CP1251");
if (loc.Error()) {
GTEST_SKIP() << "ru_RU.CP1251 locale is not available: " << loc.Error();
}
- // strncasecmp is locale-dependent, not sure about strnicmp
+ // - strncasecmp in glibc is locale-dependent.
+ // - not sure about strnicmp (probably also locale-dependent).
+ // - strncasecmp in musl and bionic ignores locales.
+ // - ASan interceptor for strncasecmp ignores locales
{
- TCaseInsensitiveStringBuf s1 = "\xc0\xc1\xc2"; // "АБВ"
- TCaseInsensitiveStringBuf s2 = "\xe0\xe1\xe2"; // "абв"
- EXPECT_EQ(s1, s2);
+ TCaseInsensitiveAsciiStringBuf s1 = "\xc0\xc1\xc2"; // "АБВ"
+ TCaseInsensitiveAsciiStringBuf s2 = "\xe0\xe1\xe2"; // "абв"
+ EXPECT_TRUE(s1 == s2 || s1 < s2);
}
{
- TCaseInsensitiveStringBuf s1 = "\xc0\xc1\xc3"; // "АБГ"
- TCaseInsensitiveStringBuf s2 = "\xe0\xe1\xe2"; // "абв"
- EXPECT_GT(s1, s2);
+ TCaseInsensitiveAsciiStringBuf s1 = "\xc0\xc1\xc3"; // "АБГ"
+ TCaseInsensitiveAsciiStringBuf s2 = "\xe0\xe1\xe2"; // "абв"
+ EXPECT_TRUE(s1 > s2 || s1 < s2);
}
}
diff --git a/library/cpp/protobuf/json/config.h b/library/cpp/protobuf/json/config.h
index d17ac7b15b8..98d484cdf44 100644
--- a/library/cpp/protobuf/json/config.h
+++ b/library/cpp/protobuf/json/config.h
@@ -86,15 +86,18 @@ namespace NProtobufJson {
/// Print map as object, otherwise print it as array of key/value objects
bool MapAsObject = false;
- /// Stringify long integers which are not exactly representable by float or double values
enum EStringifyNumbersMode {
StringifyLongNumbersNever = 0, // default
StringifyLongNumbersForFloat,
StringifyLongNumbersForDouble,
StringifyInt64Always,
};
+ /// Stringify long integers which are not exactly representable by float or double values. Not affect repeated numbers, for repeated use StringifyNumbersRepeated
EStringifyNumbersMode StringifyNumbers = StringifyLongNumbersNever;
+ /// Stringify repeated long integers which are not exactly representable by float or double values. May cause heterogenous arrays, use StringifyInt64Always or StringifyLongNumbersNever to avoid
+ EStringifyNumbersMode StringifyNumbersRepeated = StringifyLongNumbersNever;
+
/// Decode Any fields content
bool ConvertAny = false;
@@ -191,6 +194,11 @@ namespace NProtobufJson {
return *this;
}
+ TSelf& SetStringifyNumbersRepeated(EStringifyNumbersMode stringify) {
+ StringifyNumbersRepeated = stringify;
+ return *this;
+ }
+
TSelf& SetNameGenerator(TNameGenerator callback) {
NameGenerator = callback;
return *this;
@@ -210,6 +218,7 @@ namespace NProtobufJson {
ConvertAny = value;
return *this;
}
+
};
}
diff --git a/library/cpp/protobuf/json/proto2json_printer.cpp b/library/cpp/protobuf/json/proto2json_printer.cpp
index bf0f9eb60d4..5d0e1406151 100644
--- a/library/cpp/protobuf/json/proto2json_printer.cpp
+++ b/library/cpp/protobuf/json/proto2json_printer.cpp
@@ -360,6 +360,19 @@ namespace NProtobufJson {
break; \
}
+#define REPEATED_INT_FIELD_TO_JSON(EProtoCppType, ProtoGet) \
+ case FieldDescriptor::EProtoCppType: { \
+ for (size_t i = 0, endI = reflection->FieldSize(proto, &field); i < endI; ++i) { \
+ const auto value = reflection->ProtoGet(proto, &field, i); \
+ if (NeedStringifyRepeatedNumber(value)) { \
+ json.Write(ToString(value)); \
+ } else { \
+ json.Write(value); \
+ } \
+ } \
+ break; \
+ }
+
const Reflection* reflection = proto.GetReflection();
if (reflection->FieldSize(proto, &field) > 0) {
@@ -371,10 +384,10 @@ namespace NProtobufJson {
}
switch (field.cpp_type()) {
- REPEATED_FIELD_TO_JSON(CPPTYPE_INT32, GetRepeatedInt32);
- REPEATED_FIELD_TO_JSON(CPPTYPE_INT64, GetRepeatedInt64);
- REPEATED_FIELD_TO_JSON(CPPTYPE_UINT32, GetRepeatedUInt32);
- REPEATED_FIELD_TO_JSON(CPPTYPE_UINT64, GetRepeatedUInt64);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_INT32, GetRepeatedInt32);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_INT64, GetRepeatedInt64);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_UINT32, GetRepeatedUInt32);
+ REPEATED_INT_FIELD_TO_JSON(CPPTYPE_UINT64, GetRepeatedUInt64);
REPEATED_FIELD_TO_JSON(CPPTYPE_DOUBLE, GetRepeatedDouble);
REPEATED_FIELD_TO_JSON(CPPTYPE_FLOAT, GetRepeatedFloat);
REPEATED_FIELD_TO_JSON(CPPTYPE_BOOL, GetRepeatedBool);
@@ -596,4 +609,23 @@ namespace NProtobufJson {
return false;
}
+ template <class T>
+ bool TProto2JsonPrinter::NeedStringifyRepeatedNumber(T value) const {
+ constexpr long SAFE_INTEGER_RANGE_FLOAT = 1L << 24;
+ constexpr long long SAFE_INTEGER_RANGE_DOUBLE = 1LL << 53;
+
+ switch (GetConfig().StringifyNumbersRepeated) {
+ case TProto2JsonConfig::StringifyLongNumbersNever:
+ return false;
+ case TProto2JsonConfig::StringifyLongNumbersForFloat:
+ return !ValueInRange(value, SAFE_INTEGER_RANGE_FLOAT);
+ case TProto2JsonConfig::StringifyLongNumbersForDouble:
+ return !ValueInRange(value, SAFE_INTEGER_RANGE_DOUBLE);
+ case TProto2JsonConfig::StringifyInt64Always:
+ return std::is_same_v<T, i64> || std::is_same_v<T, ui64>;
+ }
+
+ return false;
+ }
+
}
diff --git a/library/cpp/protobuf/json/proto2json_printer.h b/library/cpp/protobuf/json/proto2json_printer.h
index 360e66ad2f6..c3547dab6b2 100644
--- a/library/cpp/protobuf/json/proto2json_printer.h
+++ b/library/cpp/protobuf/json/proto2json_printer.h
@@ -60,6 +60,9 @@ namespace NProtobufJson {
template <class T>
bool NeedStringifyNumber(T value) const;
+ template <class T>
+ bool NeedStringifyRepeatedNumber(T value) const;
+
bool TryPrintAny(const NProtoBuf::Message& proto, IJsonOutput& json);
void PrintFields(const NProtoBuf::Message& proto, IJsonOutput& json);
diff --git a/library/cpp/protobuf/json/ut/proto2json_ut.cpp b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
index 9b74e737523..9e98ab8a067 100644
--- a/library/cpp/protobuf/json/ut/proto2json_ut.cpp
+++ b/library/cpp/protobuf/json/ut/proto2json_ut.cpp
@@ -1086,6 +1086,199 @@ Y_UNIT_TEST(TestStringifyNumbers) {
#undef TEST_SINGLE
} // TestStringifyNumbers
+Y_UNIT_TEST(TestStringifyNumbersRepeated) {
+#define TEST_SINGLE(flag, field, value) \
+ do { \
+ TFlatRepeated proto; \
+ proto.Add##field(value); \
+ \
+ TStringStream jsonStr; \
+ TProto2JsonConfig config; \
+ config.SetStringifyNumbers(flag); \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[" #value "]}"); \
+ } while (false)
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 10000000000000000);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 1);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 10000000000000000);
+
+#undef TEST_SINGLE
+} // TestStringifyNumbersRepeated
+
+Y_UNIT_TEST(TestStringifyNumbersRepeatedStringification){
+#define TEST_SINGLE(flag, field, value, expectString) \
+ do { \
+ TFlatRepeated proto; \
+ proto.Add##field(value); \
+ \
+ TStringStream jsonStr; \
+ TProto2JsonConfig config; \
+ config.SetStringifyNumbersRepeated(flag); \
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonStr, config)); \
+ if (expectString) { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[\"" #value "\"]}"); \
+ } else { \
+ UNIT_ASSERT_EQUAL(jsonStr.Str(), "{\"" #field "\":[" #value "]}"); \
+ } \
+ } while (false)
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, 10000000000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersNever, SI64, -10000000000000000, false);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForDouble, SI64, -10000000000000000, true);
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -1000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyLongNumbersForFloat, SI64, -10000000000000000, true);
+
+
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 1, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI32, 1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, UI64, 10000000000000000, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -1, true);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI32, -1000000000, false);
+ TEST_SINGLE(TProto2JsonConfig::StringifyInt64Always, SI64, -10000000000000000, true);
+
+#undef TEST_SINGLE
+} // TestStringifyNumbersRepeatedStringification
+
+Y_UNIT_TEST(TestStringifyNumbersRepeatedStringificationList){
+ using NJson::JSON_STRING;
+ using NJson::JSON_UINTEGER;
+ using NJson::JSON_INTEGER;
+
+ TFlatRepeated proto;
+ proto.AddUI64(1);
+ proto.AddUI64(1000000000);
+ proto.AddUI64(10000000000000000);
+ proto.AddSI64(1);
+ proto.AddSI64(1000000000);
+ proto.AddSI64(10000000000000000);
+ proto.AddSI64(-1);
+ proto.AddSI64(-1000000000);
+ proto.AddSI64(-10000000000000000);
+ proto.AddUI32(1);
+ proto.AddUI32(1000000000);
+ proto.AddSI32(-1);
+ proto.AddSI32(-1000000000);
+
+ TProto2JsonConfig config;
+ NJson::TJsonValue jsonValue;
+ THashMap<TString, NJson::TJsonValue> jsonMap;
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersNever);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_UINTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_INTEGER);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersForDouble);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_INTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_UINTEGER);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyLongNumbersForFloat);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_STRING);
+ }
+ {
+ jsonValue = NJson::TJsonValue{};
+ config.SetStringifyNumbersRepeated(TProto2JsonConfig::StringifyInt64Always);
+
+ UNIT_ASSERT_NO_EXCEPTION(Proto2Json(proto, jsonValue, config));
+ jsonMap = jsonValue.GetMap();
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[0].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI64")[2].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[0].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[1].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[2].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[3].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[4].GetType(), JSON_STRING);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI64")[5].GetType(), JSON_STRING);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[0].GetType(), JSON_INTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("SI32")[1].GetType(), JSON_INTEGER);
+
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[0].GetType(), JSON_UINTEGER);
+ UNIT_ASSERT_EQUAL(jsonMap.at("UI32")[1].GetType(), JSON_UINTEGER);
+ }
+
+} // TestStringifyNumbersRepeatedStringificationList
+
Y_UNIT_TEST(TestExtension) {
TExtensionField proto;
proto.SetExtension(bar, 1);
diff --git a/library/cpp/tld/tlds-alpha-by-domain.txt b/library/cpp/tld/tlds-alpha-by-domain.txt
index 7237b7c346b..3b9785bb944 100644
--- a/library/cpp/tld/tlds-alpha-by-domain.txt
+++ b/library/cpp/tld/tlds-alpha-by-domain.txt
@@ -1,4 +1,4 @@
-# Version 2024060400, Last Updated Tue Jun 4 07:07:01 2024 UTC
+# Version 2024061300, Last Updated Thu Jun 13 07:07:01 2024 UTC
AAA
AARP
ABB
@@ -802,7 +802,6 @@ NA
NAB
NAGOYA
NAME
-NATURA
NAVY
NBA
NC
diff --git a/library/cpp/yt/logging/logger-inl.h b/library/cpp/yt/logging/logger-inl.h
index a334a371421..5c1f1b0b556 100644
--- a/library/cpp/yt/logging/logger-inl.h
+++ b/library/cpp/yt/logging/logger-inl.h
@@ -181,59 +181,74 @@ struct TLogMessage
TStringBuf Anchor;
};
-template <size_t Length, class... TArgs>
+template <class T>
+concept CLiteralLogFormat =
+ requires (T& t) {
+ [] (const char*) { } (t);
+ };
+
+template <class... TArgs>
TLogMessage BuildLogMessage(
const TLoggingContext& loggingContext,
const TLogger& logger,
- const char (&format)[Length],
+ TFormatString<TArgs...> format,
TArgs&&... args)
{
TMessageStringBuilder builder;
- AppendLogMessageWithFormat(&builder, loggingContext, logger, format, std::forward<TArgs>(args)...);
- return {builder.Flush(), format};
+ AppendLogMessageWithFormat(&builder, loggingContext, logger, format.Get(), std::forward<TArgs>(args)...);
+ return {builder.Flush(), format.Get()};
}
-template <class T>
+template <CFormattable T>
+ requires (!CLiteralLogFormat<std::remove_cvref_t<T>>)
TLogMessage BuildLogMessage(
const TLoggingContext& loggingContext,
const TLogger& logger,
const T& obj)
{
TMessageStringBuilder builder;
- FormatValue(&builder, obj, TStringBuf());
+ FormatValue(&builder, obj, TStringBuf("v"));
if (HasMessageTags(loggingContext, logger)) {
builder.AppendString(TStringBuf(" ("));
AppendMessageTags(&builder, loggingContext, logger);
builder.AppendChar(')');
}
- return {builder.Flush(), TStringBuf()};
+
+ if constexpr (std::same_as<TStringBuf, std::remove_cvref_t<T>>) {
+ // NB(arkady-e1ppa): This is the overload where TStringBuf
+ // falls as well as zero-argument format strings.
+ // Formerly (before static analysis) there was a special overload
+ // which guaranteed that Anchor is set to the value of said TStringBuf
+ // object. Now we have overload for TFormatString<> which fordids
+ // us having overload for TStringBuf (both have implicit ctors from
+ // string literals) thus we have to accommodate TStringBuf specifics
+ // in this if constexpr part.
+ return {builder.Flush(), obj};
+ } else {
+ return {builder.Flush(), TStringBuf()};
+ }
}
inline TLogMessage BuildLogMessage(
const TLoggingContext& loggingContext,
const TLogger& logger,
- TStringBuf message)
+ TFormatString<> format)
{
- TMessageStringBuilder builder;
- builder.AppendString(message);
- if (HasMessageTags(loggingContext, logger)) {
- builder.AppendString(TStringBuf(" ("));
- AppendMessageTags(&builder, loggingContext, logger);
- builder.AppendChar(')');
- }
- return {builder.Flush(), message};
+ return BuildLogMessage(
+ loggingContext,
+ logger,
+ format.Get());
}
-template <size_t Length>
-TLogMessage BuildLogMessage(
+inline TLogMessage BuildLogMessage(
const TLoggingContext& loggingContext,
const TLogger& logger,
- const char (&message)[Length])
+ TRuntimeFormat format)
{
return BuildLogMessage(
loggingContext,
logger,
- TStringBuf(message));
+ format.Get());
}
inline TLogMessage BuildLogMessage(
diff --git a/library/cpp/yt/logging/logger.h b/library/cpp/yt/logging/logger.h
index 5cbd6818765..4f0ed44ab7e 100644
--- a/library/cpp/yt/logging/logger.h
+++ b/library/cpp/yt/logging/logger.h
@@ -22,14 +22,6 @@
#include <atomic>
-#if (!__clang__ || __clang_major__ < 16)
- #define YT_DISABLE_FORMAT_STATIC_ANALYSIS
-#endif
-
-#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
- #include "static_analysis.h"
-#endif
-
namespace NYT::NLogging {
////////////////////////////////////////////////////////////////////////////////
@@ -304,12 +296,6 @@ void LogStructuredEvent(
#define YT_LOG_EVENT(logger, level, ...) \
YT_LOG_EVENT_WITH_ANCHOR(logger, level, nullptr, __VA_ARGS__)
-#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
- #define YT_LOG_CHECK_FORMAT(...) STATIC_ANALYSIS_CHECK_LOG_FORMAT(__VA_ARGS__)
-#else
- #define YT_LOG_CHECK_FORMAT(...)
-#endif
-
#define YT_LOG_EVENT_WITH_ANCHOR(logger, level, anchor, ...) \
do { \
const auto& logger__ = (logger)(); \
@@ -333,7 +319,6 @@ void LogStructuredEvent(
} \
\
auto loggingContext__ = ::NYT::NLogging::GetLoggingContext(); \
- YT_LOG_CHECK_FORMAT(__VA_ARGS__); \
auto message__ = ::NYT::NLogging::NDetail::BuildLogMessage(loggingContext__, logger__, __VA_ARGS__); \
\
if (!anchorUpToDate__) { \
diff --git a/library/cpp/yt/logging/static_analysis-inl.h b/library/cpp/yt/logging/static_analysis-inl.h
deleted file mode 100644
index 13cd425d581..00000000000
--- a/library/cpp/yt/logging/static_analysis-inl.h
+++ /dev/null
@@ -1,146 +0,0 @@
-#ifndef STATIC_ANALYSIS_INL_H_
-#error "Direct inclusion of this file is not allowed, include static_analysis.h"
-// For the sake of sane code completion.
-#include "static_analysis.h"
-#endif
-
-#include <library/cpp/yt/misc/preprocessor.h>
-
-#include <library/cpp/yt/string/format.h>
-
-#include <string_view>
-#include <variant> // monostate
-
-namespace NYT::NLogging::NDetail {
-
-////////////////////////////////////////////////////////////////////////////////
-
-// Tag for dispatching proper TFormatArg specialization.
-template <class T>
-struct TLoggerFormatArg
-{ };
-
-// Required for TLoggerFormatArg to inherit CFormattable concept
-// from T.
-template <class T>
- requires CFormattable<T>
-void FormatValue(TStringBuilderBase*, const TLoggerFormatArg<T>&, TStringBuf);
-
-////////////////////////////////////////////////////////////////////////////////
-
-// Stateless constexpr way of capturing arg types
-// without invoking any ctors. With the help of macros
-// can turn non-constexpr argument pack of arguments
-// into constexpr pack of types.
-template <class... TArgs>
-struct TLoggerFormatArgs
-{ };
-
-// Used for macro conversion. Purposefully undefined.
-template <class... TArgs>
-TLoggerFormatArgs<std::remove_cvref_t<TArgs>...>
-AsFormatArgs(TArgs&&...);
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <bool First, bool Second>
-struct TAnalyserDispatcher
-{
- template <class... TArgs>
- static consteval void Do(std::string_view, std::string_view, TLoggerFormatArgs<TArgs...>)
- {
- // Give up :(
- // We can't crash here, because, for example, YT_LOG_ERROR(error) exists
- // and we can't really check if error is actually TError or something else here.
- // and probably shouldn't bother trying.
- }
-};
-
-template <bool Second>
-struct TAnalyserDispatcher<true, Second>
-{
- template <class TFirst, class... TArgs>
- static consteval void Do(std::string_view str, std::string_view, TLoggerFormatArgs<TFirst, TArgs...>)
- {
- // Remove outer \"'s generated by PP_STRINGIZE.
- auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
- ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
- }
-};
-
-template <>
-struct TAnalyserDispatcher<false, true>
-{
- template <class TFirst, class TSecond, class... TArgs>
- static consteval void Do(std::string_view, std::string_view str, TLoggerFormatArgs<TFirst, TSecond, TArgs...>)
- {
- // Remove outer \"'s generated by PP_STRINGIZE.
- auto stripped = std::string_view(std::begin(str) + 1, std::size(str) - 2);
- ::NYT::NDetail::TFormatAnalyser::ValidateFormat<TLoggerFormatArg<TArgs>...>(stripped);
- }
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-// This value is never read since homogenization works for unevaluated expressions.
-inline constexpr auto InvalidToken = std::monostate{};
-
-////////////////////////////////////////////////////////////////////////////////
-
-#define PP_VA_PICK_1_IMPL(N, ...) N
-#define PP_VA_PICK_2_IMPL(_1, N, ...) N
-
-////////////////////////////////////////////////////////////////////////////////
-
-//! Parameter pack parsing.
-
-#define STATIC_ANALYSIS_CAPTURE_TYPES(...) \
- decltype(::NYT::NLogging::NDetail::AsFormatArgs(__VA_ARGS__)){}
-
-#define STATIC_ANALYSIS_FIRST_TOKEN(...) \
- PP_STRINGIZE( \
- PP_VA_PICK_1_IMPL(__VA_ARGS__ __VA_OPT__(,) ::NYT::NLogging::NDetail::InvalidToken))
-
-#define STATIC_ANALYSIS_SECOND_TOKEN(...) \
- PP_STRINGIZE(\
- PP_VA_PICK_2_IMPL( \
- __VA_ARGS__ __VA_OPT__(,) \
- ::NYT::NLogging::NDetail::InvalidToken, \
- ::NYT::NLogging::NDetail::InvalidToken))
-
-#define STATIC_ANALYSIS_FIRST_TOKEN_COND(...) \
- STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__)[0] == '\"'
-
-#define STATIC_ANALYSIS_SECOND_TOKEN_COND(...) \
- STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__)[0] == '\"'
-
-#undef STATIC_ANALYSIS_CHECK_LOG_FORMAT
-#define STATIC_ANALYSIS_CHECK_LOG_FORMAT(...) \
- ::NYT \
- ::NLogging \
- ::NDetail \
- ::TAnalyserDispatcher< \
- STATIC_ANALYSIS_FIRST_TOKEN_COND(__VA_ARGS__), \
- STATIC_ANALYSIS_SECOND_TOKEN_COND(__VA_ARGS__) \
- >::Do( \
- STATIC_ANALYSIS_FIRST_TOKEN(__VA_ARGS__), \
- STATIC_ANALYSIS_SECOND_TOKEN(__VA_ARGS__), \
- STATIC_ANALYSIS_CAPTURE_TYPES(__VA_ARGS__))
-
-////////////////////////////////////////////////////////////////////////////////
-
-} // namespace NYT::NLogging::NDetail
-
-template <class T>
-struct NYT::TFormatArg<NYT::NLogging::NDetail::TLoggerFormatArg<T>>
- : public NYT::TFormatArgBase
-{
- // We mix in '\"' and ' ' which is an artifact of logging stringize.
- // We want to support YT_LOG_XXX("Value: %" PRIu64, 42)
- // for plantform independent prints of numbers.
- // String below may be converted to a token:
- // "\"Value: %\" \"u\""
- // Thus adding a \" \" sequence.
- static constexpr auto FlagSpecifiers
- = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ T>();
-};
diff --git a/library/cpp/yt/logging/static_analysis.h b/library/cpp/yt/logging/static_analysis.h
deleted file mode 100644
index a335d8c6cc1..00000000000
--- a/library/cpp/yt/logging/static_analysis.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#pragma once
-
-namespace NYT::NLogging {
-
-////////////////////////////////////////////////////////////////////////////////
-
-// Performs a compile-time check of log arguments validity.
-// Valid argument lists are:
-// 1. (format, args...)
-// 2. (error, format, args...)
-// If format is not a string literal or argument list
-// is not valid, no check is made -- macro turns to
-// a no-op.
-#define STATIC_ANALYSIS_CHECK_LOG_FORMAT(...)
-
-////////////////////////////////////////////////////////////////////////////////
-
-} // namespace NYT::NLogging
-
-#define STATIC_ANALYSIS_INL_H_
-#include "static_analysis-inl.h"
-#undef STATIC_ANALYSIS_INL_H_
diff --git a/library/cpp/yt/logging/unittests/static_analysis_ut.cpp b/library/cpp/yt/logging/unittests/static_analysis_ut.cpp
index 1c705dc9674..7fb4c0150c9 100644
--- a/library/cpp/yt/logging/unittests/static_analysis_ut.cpp
+++ b/library/cpp/yt/logging/unittests/static_analysis_ut.cpp
@@ -7,32 +7,30 @@ namespace {
////////////////////////////////////////////////////////////////////////////////
-TEST(TStaticAnalysis, ValidFormats)
+const TLogger Logger{};
+
+TEST(TStaticAnalysisTest, ValidFormats)
{
- // Mock for actual error -- we only care that
- // it is some runtime object.
- [[maybe_unused]] struct TError
- { } error;
-
- YT_LOG_CHECK_FORMAT("Hello");
- YT_LOG_CHECK_FORMAT("Hello %v", "World!");
- YT_LOG_CHECK_FORMAT("Hello %qv", "World!");
- YT_LOG_CHECK_FORMAT(error);
- YT_LOG_CHECK_FORMAT(error, "Hello");
- YT_LOG_CHECK_FORMAT(error, "Hello %Qhs", "World!");
- YT_LOG_CHECK_FORMAT("Hello %%");
- YT_LOG_CHECK_FORMAT("Hello %" PRIu64, 42);
+ YT_LOG_INFO("Hello");
+ YT_LOG_INFO("Hello %v", "World!");
+ YT_LOG_INFO("Hello %qv", "World!");
+ YT_LOG_INFO(42);
+ YT_LOG_INFO("Hello %%");
+ YT_LOG_INFO("Hello %" PRIu64, 42);
+
+ TStringBuf msg = "Hello";
+ YT_LOG_INFO(msg);
}
// Uncomment this test to see that we don't have false negatives!
-// TEST(TStaticAnalysis, InvalidFormats)
+// TEST(TStaticAnalysisTest, InvalidFormats)
// {
-// YT_LOG_CHECK_FORMAT("Hello", 1);
-// YT_LOG_CHECK_FORMAT("Hello %");
-// YT_LOG_CHECK_FORMAT("Hello %false");
-// YT_LOG_CHECK_FORMAT("Hello ", "World");
-// YT_LOG_CHECK_FORMAT("Hello ", "(World: %v)", 42);
-// YT_LOG_CHECK_FORMAT("Hello %lbov", 42); // There is no 'b' flag.
+// YT_LOG_INFO("Hello", 1);
+// YT_LOG_INFO("Hello %");
+// YT_LOG_INFO("Hello %false");
+// YT_LOG_INFO("Hello ", "World");
+// YT_LOG_INFO("Hello ", "(World: %v)", 42);
+// YT_LOG_INFO("Hello %lbov", 42); // There is no 'b' flag.
// }
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/misc/optional.h b/library/cpp/yt/misc/optional.h
index d5e3c07fbe0..c5982af7bfc 100644
--- a/library/cpp/yt/misc/optional.h
+++ b/library/cpp/yt/misc/optional.h
@@ -97,12 +97,6 @@ struct TStdOptionalTraits<std::optional<T>>
} // namespace NYT
template <class T>
-TString ToString(const std::optional<T>& nullable)
-{
- return nullable ? ToString(*nullable) : "<Null>";
-}
-
-template <class T>
struct THash<std::optional<T>>
{
size_t operator()(const std::optional<T>& nullable) const
diff --git a/library/cpp/yt/misc/source_location.cpp b/library/cpp/yt/misc/source_location.cpp
index 3fe45e23a76..f3f396ee90e 100644
--- a/library/cpp/yt/misc/source_location.cpp
+++ b/library/cpp/yt/misc/source_location.cpp
@@ -10,7 +10,7 @@ namespace NYT {
#ifdef __cpp_lib_source_location
-void FormatValue(TStringBuilderBase* builder, const std::source_location& location, TStringBuf /*format*/)
+void FormatValue(TStringBuilderBase* builder, const std::source_location& location, TStringBuf /*spec*/)
{
if (location.file_name() != nullptr) {
builder->AppendFormat(
@@ -23,11 +23,6 @@ void FormatValue(TStringBuilderBase* builder, const std::source_location& locati
}
}
-TString ToString(const std::source_location& location)
-{
- return ToStringViaBuilder(location);
-}
-
#endif // __cpp_lib_source_location
////////////////////////////////////////////////////////////////////////////////
@@ -75,6 +70,25 @@ bool TSourceLocation::operator==(const TSourceLocation& other) const
Line_ == other.Line_;
}
+#ifdef __cpp_lib_source_location
+TSourceLocation TSourceLocation::FromStd(const std::source_location& location)
+{
+ return TSourceLocation(location.file_name(), location.line());
+}
+#endif // __cpp_lib_source_location
+
+void FormatValue(TStringBuilderBase* builder, const TSourceLocation& location, TStringBuf /*spec*/)
+{
+ if (location.GetFileName() != nullptr) {
+ builder->AppendFormat(
+ "%v:%v",
+ location.GetFileName(),
+ location.GetLine());
+ } else {
+ builder->AppendString("<unknown>");
+ }
+}
+
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT
diff --git a/library/cpp/yt/misc/source_location.h b/library/cpp/yt/misc/source_location.h
index 38a6f83c804..0496a4e4967 100644
--- a/library/cpp/yt/misc/source_location.h
+++ b/library/cpp/yt/misc/source_location.h
@@ -10,13 +10,12 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
+class TStringBuilderBase;
+
// TODO(dgolear): Drop when LLVM-14 is eradicated.
#ifdef __cpp_lib_source_location
-class TStringBuilderBase;
-
-void FormatValue(TStringBuilderBase* builder, const std::source_location& location, TStringBuf /*format*/);
-TString ToString(const std::source_location& location);
+void FormatValue(TStringBuilderBase* builder, const std::source_location& location, TStringBuf /*spec*/);
#endif // __cpp_lib_source_location
@@ -42,6 +41,10 @@ public:
bool operator<(const TSourceLocation& other) const;
bool operator==(const TSourceLocation& other) const;
+#ifdef __cpp_lib_source_location
+ static TSourceLocation FromStd(const std::source_location& location);
+#endif // __cpp_lib_source_location
+
private:
const char* FileName_;
int Line_;
@@ -49,7 +52,13 @@ private:
};
//! Defines a macro to record the current source location.
+#ifdef __cpp_lib_source_location
+#define FROM_HERE ::NYT::TSourceLocation::FromStd(std::source_location::current())
+#else
#define FROM_HERE ::NYT::TSourceLocation(__FILE__, __LINE__)
+#endif // __cpp_lib_source_location
+
+void FormatValue(TStringBuilderBase* builder, const TSourceLocation& location, TStringBuf spec);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/misc/strong_typedef.h b/library/cpp/yt/misc/strong_typedef.h
index d47581701d6..58d96ebd2cf 100644
--- a/library/cpp/yt/misc/strong_typedef.h
+++ b/library/cpp/yt/misc/strong_typedef.h
@@ -56,13 +56,6 @@ public:
private:
T Underlying_;
-
- //! NB: Hidden friend definition to make this name accessible only via ADL.
- friend TString ToString(const TStrongTypedef& value)
- requires requires (T value) { { ToString(value) } -> std::same_as<TString>; }
- {
- return ToString(value.Underlying_);
- }
};
#define YT_DEFINE_STRONG_TYPEDEF(T, TUnderlying) \
diff --git a/library/cpp/yt/misc/variant-inl.h b/library/cpp/yt/misc/variant-inl.h
index 01f0e08593a..4b0e04c92c7 100644
--- a/library/cpp/yt/misc/variant-inl.h
+++ b/library/cpp/yt/misc/variant-inl.h
@@ -45,12 +45,6 @@ void FormatValue(TStringBuilderBase* builder, const std::variant<Ts...>& variant
NDetail::TVariantFormatter<0, Ts...>::Do(builder, variant, spec);
}
-template <class... Ts>
-TString ToString(const std::variant<Ts...>& variant)
-{
- return ToStringViaBuilder(variant);
-}
-
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT
diff --git a/library/cpp/yt/misc/variant.cpp b/library/cpp/yt/misc/variant.cpp
index be86affef8f..df78563d0c9 100644
--- a/library/cpp/yt/misc/variant.cpp
+++ b/library/cpp/yt/misc/variant.cpp
@@ -6,7 +6,7 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-void FormatValue(TStringBuilderBase* builder, const std::monostate&, TStringBuf /*format*/)
+void FormatValue(TStringBuilderBase* builder, const std::monostate&, TStringBuf /*spec*/)
{
builder->AppendString(TStringBuf("<monostate>"));
}
diff --git a/library/cpp/yt/misc/variant.h b/library/cpp/yt/misc/variant.h
index 4ca977873e7..9e8e1220897 100644
--- a/library/cpp/yt/misc/variant.h
+++ b/library/cpp/yt/misc/variant.h
@@ -12,10 +12,7 @@ class TStringBuilderBase;
template <class... Ts>
void FormatValue(TStringBuilderBase* builder, const std::variant<Ts...>& variant, TStringBuf spec);
-void FormatValue(TStringBuilderBase* builder, const std::monostate&, TStringBuf /*format*/);
-
-template <class... Ts>
-TString ToString(const std::variant<Ts...>& variant);
+void FormatValue(TStringBuilderBase* builder, const std::monostate&, TStringBuf /*spec*/);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h
index 069538d84bf..1ff2d816c5c 100644
--- a/library/cpp/yt/string/format-inl.h
+++ b/library/cpp/yt/string/format-inl.h
@@ -473,19 +473,19 @@ template <class T, class TPolicy>
void FormatValue(TStringBuilderBase* builder, const TMaybe<T, TPolicy>& value, TStringBuf spec);
// std::optional
-template <class T>
+template <CFormattable T>
void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec);
// std::pair
-template <class A, class B>
+template <CFormattable A, CFormattable B>
void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec);
// std::tuple
-template <class... Ts>
+template <CFormattable... Ts>
void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec);
// TEnumIndexedArray
-template <class E, class T>
+template <class E, CFormattable T>
void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec);
// One-valued ranges
@@ -531,7 +531,7 @@ inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf
}
// std::optional: generic T
-template <class T>
+template <CFormattable T>
void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec)
{
if (value.has_value()) {
@@ -542,7 +542,7 @@ void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TSt
}
// std::pair
-template <class A, class B>
+template <CFormattable A, CFormattable B>
void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec)
{
builder->AppendChar('{');
@@ -553,7 +553,7 @@ void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStr
}
// std::tuple
-template <class... Ts>
+template <CFormattable... Ts>
void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec)
{
builder->AppendChar('{');
@@ -571,7 +571,7 @@ void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TS
}
// TEnumIndexedArray
-template <class E, class T>
+template <class E, CFormattable T>
void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec)
{
builder->AppendChar('{');
@@ -712,12 +712,12 @@ concept CFormatter = CInvocable<T, void(size_t, TStringBuilderBase*, TStringBuf)
template <CFormatter TFormatter>
void RunFormatter(
TStringBuilderBase* builder,
- TStringBuf fmt,
+ TStringBuf format,
const TFormatter& formatter)
{
size_t argIndex = 0;
- auto current = std::begin(fmt);
- auto end = std::end(fmt);
+ auto current = std::begin(format);
+ auto end = std::end(format);
while (true) {
// Scan verbatim part until stop symbol.
auto verbatimBegin = current;
@@ -819,44 +819,26 @@ void RunFormatter(
////////////////////////////////////////////////////////////////////////////////
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args)
-{
- NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...);
- NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter);
-}
-
-template <class... TArgs>
-void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args)
-{
- // NB(arkady-e1ppa): StaticFormat performs the
- // formattability check of the args in a way
- // that provides more useful information
- // than a simple static_assert with conjunction.
- // Additionally, the latter doesn't work properly
- // for older clang version.
- static constexpr auto argsChecker = [] {
- TStaticFormat<TArgs...>::CheckFormattability();
- return 42;
- } ();
- Y_UNUSED(argsChecker);
-
- NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...);
- NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter);
-}
-
-template <class... TArgs>
-TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args)
-{
- TStringBuilder builder;
- Format(&builder, fmt, std::forward<TArgs>(args)...);
- return builder.Flush();
+void Format(TStringBuilderBase* builder, TFormatString<TArgs...> format, TArgs&&... args)
+{
+ // NB(arkady-e1ppa): "if constexpr" is done in order to prevent
+ // compiler from emitting "No matching function to call"
+ // when arguments are not formattable.
+ // Compiler would crash in TFormatString ctor
+ // anyway (e.g. program would not compile) but
+ // for some reason it does look ahead and emits
+ // a second error.
+ if constexpr ((CFormattable<TArgs> && ...)) {
+ NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...);
+ NYT::NDetail::RunFormatter(builder, format.Get(), formatter);
+ }
}
template <class... TArgs>
-TString Format(TRuntimeFormat fmt, TArgs&&... args)
+TString Format(TFormatString<TArgs...> format, TArgs&&... args)
{
TStringBuilder builder;
- Format(&builder, fmt, std::forward<TArgs>(args)...);
+ Format(&builder, format, std::forward<TArgs>(args)...);
return builder.Flush();
}
@@ -865,40 +847,40 @@ TString Format(TRuntimeFormat fmt, TArgs&&... args)
template <size_t Length, class TVector>
void FormatVector(
TStringBuilderBase* builder,
- const char (&fmt)[Length],
+ const char (&format)[Length],
const TVector& vec)
{
NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec);
- NYT::NDetail::RunFormatter(builder, fmt, formatter);
+ NYT::NDetail::RunFormatter(builder, format, formatter);
}
template <class TVector>
void FormatVector(
TStringBuilderBase* builder,
- TStringBuf fmt,
+ TStringBuf format,
const TVector& vec)
{
NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec);
- NYT::NDetail::RunFormatter(builder, fmt, formatter);
+ NYT::NDetail::RunFormatter(builder, format, formatter);
}
template <size_t Length, class TVector>
TString FormatVector(
- const char (&fmt)[Length],
+ const char (&format)[Length],
const TVector& vec)
{
TStringBuilder builder;
- FormatVector(&builder, fmt, vec);
+ FormatVector(&builder, format, vec);
return builder.Flush();
}
template <class TVector>
TString FormatVector(
- TStringBuf fmt,
+ TStringBuf format,
const TVector& vec)
{
TStringBuilder builder;
- FormatVector(&builder, fmt, vec);
+ FormatVector(&builder, format, vec);
return builder.Flush();
}
diff --git a/library/cpp/yt/string/format.h b/library/cpp/yt/string/format.h
index 6463135f89c..62b8726ddbe 100644
--- a/library/cpp/yt/string/format.h
+++ b/library/cpp/yt/string/format.h
@@ -55,14 +55,9 @@ namespace NYT {
*/
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args);
+void Format(TStringBuilderBase* builder, TFormatString<TArgs...> format, TArgs&&... args);
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args);
-
-template <class... TArgs>
-TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args);
-template <class... TArgs>
-TString Format(TRuntimeFormat fmt, TArgs&&... args);
+TString Format(TFormatString<TArgs...> format, TArgs&&... args);
////////////////////////////////////////////////////////////////////////////////
@@ -142,7 +137,7 @@ class TLazyMultiValueFormatter
: private TNonCopyable
{
public:
- TLazyMultiValueFormatter(TStringBuf fmt, TArgs&&... args);
+ TLazyMultiValueFormatter(TStringBuf format, TArgs&&... args);
// NB(arkady-e1ppa): We actually have to
// forward declare this method as above
@@ -162,7 +157,7 @@ private:
};
template <class ... Args>
-auto MakeLazyMultiValueFormatter(TStringBuf fmt, Args&&... args);
+auto MakeLazyMultiValueFormatter(TStringBuf format, Args&&... args);
////////////////////////////////////////////////////////////////////////////////
@@ -175,23 +170,23 @@ auto MakeLazyMultiValueFormatter(TStringBuf fmt, Args&&... args);
template <size_t Length, class TVector>
void FormatVector(
TStringBuilderBase* builder,
- const char (&fmt)[Length],
+ const char (&format)[Length],
const TVector& vec);
template <class TVector>
void FormatVector(
TStringBuilderBase* builder,
- TStringBuf fmt,
+ TStringBuf format,
const TVector& vec);
template <size_t Length, class TVector>
TString FormatVector(
- const char (&fmt)[Length],
+ const char (&format)[Length],
const TVector& vec);
template <class TVector>
TString FormatVector(
- TStringBuf fmt,
+ TStringBuf format,
const TVector& vec);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/format_analyser-inl.h b/library/cpp/yt/string/format_analyser-inl.h
index ecc83ebccb4..8a3dd897ffc 100644
--- a/library/cpp/yt/string/format_analyser-inl.h
+++ b/library/cpp/yt/string/format_analyser-inl.h
@@ -16,7 +16,7 @@ consteval bool Contains(std::string_view sv, char symbol)
}
template <class... TArgs>
-consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
+consteval void TFormatAnalyser::ValidateFormat(std::string_view format)
{
std::array<std::string_view, sizeof...(TArgs)> markers = {};
std::array<TSpecifiers, sizeof...(TArgs)> specifiers{GetSpecifiers<TArgs>()...};
@@ -24,14 +24,14 @@ consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
int markerCount = 0;
int currentMarkerStart = -1;
- for (int idx = 0; idx < std::ssize(fmt); ++idx) {
- auto symbol = fmt[idx];
+ for (int index = 0; index < std::ssize(format); ++index) {
+ auto symbol = format[index];
// Parse verbatim text.
if (currentMarkerStart == -1) {
if (symbol == IntroductorySymbol) {
// Marker maybe begins.
- currentMarkerStart = idx;
+ currentMarkerStart = index;
}
continue;
}
@@ -41,7 +41,7 @@ consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
// we need markerCount to be within range of our
// specifier array.
if (symbol == IntroductorySymbol) {
- if (currentMarkerStart + 1 != idx) {
+ if (currentMarkerStart + 1 != index) {
// '%a% detected'
CrashCompilerWrongTermination("You may not terminate flag sequence other than %% with \'%\' symbol");
return;
@@ -62,7 +62,7 @@ consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt)
// Marker has finished.
markers[markerCount]
- = std::string_view(fmt.begin() + currentMarkerStart, idx - currentMarkerStart + 1);
+ = std::string_view(format.begin() + currentMarkerStart, index - currentMarkerStart + 1);
currentMarkerStart = -1;
++markerCount;
diff --git a/library/cpp/yt/string/format_arg-inl.h b/library/cpp/yt/string/format_arg-inl.h
index 82a77a1249a..e2f08e540dc 100644
--- a/library/cpp/yt/string/format_arg-inl.h
+++ b/library/cpp/yt/string/format_arg-inl.h
@@ -27,6 +27,13 @@ constexpr bool IsNYTName()
return qualidName.find("NYT::") == 0;
}
+template <class T>
+constexpr bool IsStdName()
+{
+ constexpr auto qualidName = QualidName<T>();
+ return qualidName.find("std::") == 0;
+}
+
} // namespace NDetail
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/format_arg.h b/library/cpp/yt/string/format_arg.h
index 8c4f354f98e..6240059ed93 100644
--- a/library/cpp/yt/string/format_arg.h
+++ b/library/cpp/yt/string/format_arg.h
@@ -21,9 +21,6 @@ constexpr std::string_view QualidName();
template <class T>
constexpr bool IsNYTName();
-template <class T>
-concept CYtName = IsNYTName<T>();
-
} // namespace NDetail
////////////////////////////////////////////////////////////////////////////////
@@ -35,10 +32,10 @@ struct TFormatArgBase
public:
// TODO(arkady-e1ppa): Consider more strict formatting rules.
static constexpr std::array ConversionSpecifiers = {
- 'v', '1', 'c', 's', 'd', 'i', 'o',
- 'x', 'X', 'u', 'f', 'F', 'e', 'E',
- 'a', 'A', 'g', 'G', 'n', 'p'
- };
+ 'v', '1', 'c', 's', 'd', 'i', 'o',
+ 'x', 'X', 'u', 'f', 'F', 'e', 'E',
+ 'a', 'A', 'g', 'G', 'n', 'p'
+ };
static constexpr std::array FlagSpecifiers = {
'-', '+', ' ', '#', '0',
diff --git a/library/cpp/yt/string/format_string-inl.h b/library/cpp/yt/string/format_string-inl.h
index 5187a2ac6b0..a692d9648d0 100644
--- a/library/cpp/yt/string/format_string-inl.h
+++ b/library/cpp/yt/string/format_string-inl.h
@@ -11,7 +11,7 @@ namespace NYT {
template <class... TArgs>
template <class T>
requires std::constructible_from<std::string_view, T>
-consteval TBasicStaticFormat<TArgs...>::TBasicStaticFormat(const T& fmt)
+consteval TBasicFormatString<TArgs...>::TBasicFormatString(const T& fmt)
: Format_(fmt)
{
CheckFormattability();
@@ -21,13 +21,13 @@ consteval TBasicStaticFormat<TArgs...>::TBasicStaticFormat(const T& fmt)
}
template <class... TArgs>
-TStringBuf TBasicStaticFormat<TArgs...>::Get() const noexcept
+TStringBuf TBasicFormatString<TArgs...>::Get() const noexcept
{
return {Format_};
}
template <class... TArgs>
-consteval void TBasicStaticFormat<TArgs...>::CheckFormattability()
+consteval void TBasicFormatString<TArgs...>::CheckFormattability()
{
#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS)
using TTuple = std::tuple<std::remove_cvref_t<TArgs>...>;
@@ -42,13 +42,21 @@ consteval void TBasicStaticFormat<TArgs...>::CheckFormattability()
#endif
}
-inline TRuntimeFormat::TRuntimeFormat(TStringBuf fmt)
- : Format_(fmt)
-{ }
-
-inline TStringBuf TRuntimeFormat::Get() const noexcept
+template <class... TArgs>
+TBasicFormatString<TArgs...>::TBasicFormatString(TRuntimeFormat fmt)
+ : Format_(fmt.Get())
{
- return Format_;
+ // NB(arkady-e1ppa): StaticFormat performs the
+ // formattability check of the args in a way
+ // that provides more useful information
+ // than a simple static_assert with conjunction.
+ // Additionally, the latter doesn't work properly
+ // for older clang version.
+ static constexpr auto argsChecker = [] {
+ CheckFormattability();
+ return 42;
+ } ();
+ Y_UNUSED(argsChecker);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/format_string.cpp b/library/cpp/yt/string/format_string.cpp
new file mode 100644
index 00000000000..31aae3ace13
--- /dev/null
+++ b/library/cpp/yt/string/format_string.cpp
@@ -0,0 +1,18 @@
+#include "format_string.h"
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+TRuntimeFormat::TRuntimeFormat(TStringBuf fmt)
+ : Format_(fmt)
+{ }
+
+TStringBuf TRuntimeFormat::Get() const noexcept
+{
+ return Format_;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/string/format_string.h b/library/cpp/yt/string/format_string.h
index 8eeab7d99df..0c347637732 100644
--- a/library/cpp/yt/string/format_string.h
+++ b/library/cpp/yt/string/format_string.h
@@ -12,17 +12,32 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
+// Explicitly create TRuntimeFormat if you wish to
+// use runtime/non-literal value as format.
+class TRuntimeFormat
+{
+public:
+ explicit TRuntimeFormat(TStringBuf fmt);
+
+ TStringBuf Get() const noexcept;
+
+private:
+ TStringBuf Format_;
+};
+
// This class used to properly bind to
// string literals and allow compile-time parsing/checking
// of those. If you need a runtime format, use TRuntimeFormat
template <class... TArgs>
-class TBasicStaticFormat
+class TBasicFormatString
{
public:
// Can be used to perform compile-time check of format.
template <class T>
requires std::constructible_from<std::string_view, T>
- consteval TBasicStaticFormat(const T& fmt);
+ consteval TBasicFormatString(const T& fmt);
+
+ TBasicFormatString(TRuntimeFormat fmt);
TStringBuf Get() const noexcept;
@@ -35,22 +50,9 @@ private:
static void CrashCompilerClassIsNotFormattable();
};
-// Explicitly create TRuntimeFormat if you wish to
-// use runtime/non-literal value as format.
-class TRuntimeFormat
-{
-public:
- inline explicit TRuntimeFormat(TStringBuf fmt);
-
- inline TStringBuf Get() const noexcept;
-
-private:
- TStringBuf Format_;
-};
-
// Used to properly infer template arguments if Format.
template <class... TArgs>
-using TStaticFormat = TBasicStaticFormat<std::type_identity_t<TArgs>...>;
+using TFormatString = TBasicFormatString<std::type_identity_t<TArgs>...>;
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/readme.md b/library/cpp/yt/string/readme.md
new file mode 100644
index 00000000000..1e19eddb53c
--- /dev/null
+++ b/library/cpp/yt/string/readme.md
@@ -0,0 +1,314 @@
+# [library/cpp/yt/string]
+
+## Structure
+
+This library provides ways of printing data structures as well as some helpers methods to work with strings.
+
+### `TStringBuilder` [library/cpp/yt/string/string_builder.h]
+
+String formatter with dynamic buffer which supports strings, chars and arbitrary Format expressions (see below).
+```cpp
+TString HelloWorld()
+{
+ TStringBuilder builder;
+ builder.AppendString("Hello,"); // <- Dynamic allocation of max(minSize, str.len()) bytes.
+ builder.AppendChar(' ');
+ builder.AppendFormat("World %v!", 42); // See Format section below
+ return builder.Flush(); // Hello, World 42!
+}
+```
+
+### `TRawFormatter` [library/cpp/yt/string/raw_formatter.h]
+
+String formatter with static buffer which is stored on stack frame. Supports strings, chars, numbers and guids.
+```cpp
+TString HelloWorld(TGuid guid) // guid = "1-2-3-4"
+{
+ TRawFormatter<42> builder; // <- Buffer size is set right away. Never allocates.
+ builder.AppendString("Hello");
+ builder.AppendChar(' ');
+ builder.AppendString("World ");
+ builder.AppendGuid(guid);
+ builder.AppendChar(' ');
+ builder.AppendNumber(42);
+ builder.AppendChar('!');
+ return TString(builder.GetBuffer()); // Hello World 1-2-3-4 42!
+}
+```
+
+Attempt to append string which results in buffer overflow truncates the string
+```cpp
+TString LongMessage()
+{
+ TRawFormatter<7> builder;
+ builder.AppendString("Hello World!");
+ return TString(builder.GetBuffer()); // Hello W
+}
+```
+
+### `Format` [library/cpp/yt/string/format.h]
+
+Universal way of generating strings in a fashion similar to `printf` with flags support.
+```cpp
+Format("Hello, World %d!", 42); // Hello, World 42!
+```
+
+Currently all std flags are supported via fallback to `printf` (Note: this is subject to change. We might remove support of the majority of flags in the future to reduce complexity on the user side). We additionally support "Universal" conversion specifier -- "v" which prints value in a certain default way.
+```cpp
+Format("Hello, World %v!", 42); // Hello, World 42!
+
+Format("Value is %v", "MyValue"); // Value is MyValue
+
+Format("Vector is %v", std::vector{1, 2, 3}); // Vector is [1, 2, 3]
+```
+
+"l" specifier can be applied to enums and bools to emit them as their lowercase versions:
+```cpp
+DEFINE_ENUM(EMyEnum,
+((MyValue1) (42))
+((AnotherValue) (41))
+);
+
+Format("%v", true); // True
+Format("%v", EMyEnum::MyValue1); // MyValue1
+
+Format("%lv", true); // true
+Format("%lv", EMyEnum::MyValue); // my_value1
+```
+
+"q" and "Q" specifiers wrap output into quotations marks ' and " respectively. If the same quotation marks are detected inside the formattable value, they are replaced by their "\\"-version:
+```cpp
+Format("%Qv", true); // "True"
+Format("%qv", true); // 'true'
+Format("%Qv", "\"Hello World\""); // "\"Hello World\""
+
+// std::array{"MyValue1", "AnotherValue"}
+auto names = TEnumTraits<EMyEnum>::GetDomainNames();
+Format("%Qv", names); // "[\"MyValue1\", \"AnotherValue\"]"
+```
+
+`FormatterWrapper` allows conditional writes into the string:
+```cpp
+NYT::Format(
+ "Value is %v%v",
+ 42,
+ MakeFormatterWrapper([&] (auto* builder) {
+ If (PossiblyMissingInfo_) {
+ builder->AppendString(", PossiblyMissingInfo: ");
+ FormatValue(builder, PossiblyMissingInfo_, "v");
+ }
+ }));
+```
+
+`FormatVector` allows treating range of values as a generator coroutine returning values for each placeholder:
+```cpp
+FormatVector("One: %v, Two: %v, Three: %v", {1, 2, 3})
+// One: 1, Two: 2, Three: 3
+```
+
+### Customising Format
+
+By default type is not Formattable:
+```cpp
+struct TMyStruct
+{ };
+
+static_assert(!CFormattable<TMyStruct>);
+Format("%v", TMyStruct{}); // <- Results in CE
+```
+Compiler error looks like this:
+```cpp
+ROOT/library/cpp/yt/string/unittests/format_ut.cpp:46:36: error: call to consteval function 'NYT::TBasicStaticFormat<NYT::(anonymous namespace)::TMyStruct>::TBasicStaticFormat<char[3]>' is not a constant expression
+[[maybe_unused]] auto val = Format("%v", TMyStruct{});
+ ^
+ROOT/library/cpp/yt/string/format_string-inl.h:38:17: note: non-constexpr function 'CrashCompilerClassIsNotFormattable<NYT::(anonymous namespace)::TMyStruct>' cannot be used in a constant expression
+ CrashCompilerClassIsNotFormattable<std::tuple_element_t<Idx, TTuple>>();
+ ^
+ROOT/library/cpp/yt/string/format_string-inl.h:36:10: note: in call to '&[] {
+ if (!CFormattable<std::tuple_element_t<0UL, TTuple>>) {
+ CrashCompilerClassIsNotFormattable<std::tuple_element_t<0UL, TTuple>>();
+ }
+}->operator()()'
+...
+```
+
+First line contains the source location where the error occured. Second line contains the function name `CrashCompilerClassIsNotFormattable<NYT::(anonymous namespace)::TMyStruct>` which name is the error and template argument is the errorneos type. There are some more lines which would contain incomprehensible garbage --- don't bother reading it. Other compiler errors generated by static analyser (see below) follow the same structure.
+
+In order to support printing custom type, one must create an overload of `FormatValue` function. If everything is done correctly, concept `CFormattable<T>` should be satisfied and the value printed accordingly.
+
+```cpp
+struct TMyStruct
+{ };
+
+void FormatValue(TStringBuilderBase* builder, const TMyStruct& /*val*/, TStringBuf /*spec*/)
+{
+ builder->AppendString(TStringBuf("TMyStruct"));
+}
+
+static_assert(CFormattable<TMyStruct>);
+Format("%v", TMyStruct{}); // "TMyStruct"
+```
+
+First argument is already known builder (technically, the part of builder which can be written into, but not flushed). Second argument is the value to be formatted and the `spec` is the set of flags to be applied during the formatting. Spec must not be empty or contain the introductory symbol '%'!
+```cpp
+struct TMyPair
+{
+ int Key;
+ TString Value;
+};
+
+void FormatValue(TStringBuilderBase* builder, const TMyPair& pair, TStringBuf spec)
+{
+ // We shall support an extra flag -- 'k' which forces pair to be printed differently
+ bool concat = false;
+
+ for (auto c : spec) {
+ concat |= (c == 'k');
+ }
+
+ if (concat) {
+ builder->AppendFormat("%v_%v", Key, Value);
+ } else {
+ builder->AppendFormat("{%v: %v}", Key, Value);
+ }
+};
+
+// Required for static analysis (see section below)
+// If you don't add extra specifiers you can ignore this part.
+template <>
+struct NYT::TFormatArg<TMyPair>
+ : public NYT::TFormatArgBase
+{
+ static constexpr auto ConversionSpecifiers = TFormatArgBase::ConversionSpecifiers;
+
+ static constexpr auto FlagSpecifiers =
+ TFormatArgBase::ExtendConversion</*Hot*/ true, 1, std::array{'k'}>();
+};
+
+Format("%v", TMyPair{42, "Hello"});
+// spec is "v"
+// output is {42: Hello}
+
+Format("%kv", TMyPair{42, "Hello"});
+// spec is "kv"
+// output is 42_Hello
+```
+
+`TRuntimeFormat` is required if you want to use a non-constexpr value as a format string:
+```cpp
+cosntexpr TStringBuf fmtGood1 = "Hello, %v";
+const char* fmtBad = "Hello, %v";
+TRuntimeFormat fmtGood2{fmtBad};
+
+Format(fmtGood1, "World!"); // Hello, World
+Format(fmdBad, "World!"); // CE --- call to consteval function is not constexpr
+Format(fmtGood2, "World!"); // Hello, World
+```
+
+### Static analysis (since clang-16)
+
+If format string can bind to `TFormatString` (that is, it is a constexpr string_view or a literal) then static analysis on supplied args is performed.
+
+#### How to disable
+
+Per-file: `#define YT_DISABLE_FORMAT_STATIC_ANALYSIS` (see [library/cpp/yt/string/format_string.h] for up to date macro name).
+
+#### What is checked
+
+Static analyser checks if the number of specifier sequences matches the number of arguments supplied. Validity of specifier sequences if checked per argument (that specifier sequence is either "%%" or starts with "%", ends with one of the conversion specifiers and contains only the flag specifiers in the middle). Lists of conversion specifiers and flags specifiers are customisation points (see [library/cpp/yt/string/format_arg.h]).
+
+#### Customising static analysis
+
+We have already seen that `TMyPair` additionally required specialization of `TFormatArg` struct in order to work. Unless you want to change the list of allowed specifiers, default definition of `TFormatArg<T>` will be sufficient. We want to add an extra flag specifier for `TMyPair` and thus we must specialize `NYT::TFormatArg<TMyPair>`:
+
+```cpp
+template <>
+struct NYT::TFormatArg<TMyPair>
+ : public NYT::TFormatArgBase // Contains default sets of specifiers and some convenience tools for customization.
+{
+ // Technically not required as it is present in base. Here written for exposition.
+ static constexpr auto ConversionSpecifiers = TFormatArgBase::ConversionSpecifiers;
+
+ // Adds 'k' flag to the list of default specifiers
+ // 'Hot' means that we prepend specifier since we expect
+ // it to be used frequently. This speeds up the compilation a little.
+ static constexpr auto FlagSpecifiers =
+ TFormatArgBase::ExtendConversion</*Hot*/ true, 1, std::array{'k'}>();
+};
+```
+
+Now we are able to print the value as format analyser is aware of the new flag 'k'. If we wanted to, we could remove the rest of the default specifiers provided by `TFormatArgBase`, since most of them might not make any sence for your type.
+
+You can use `TFormatArg` + `FormatValue` to fully support format decorators:
+```cpp
+template <class T>
+struct TDecorator
+{
+ T Value;
+};
+
+template <class T>
+struct NYT::TFormatArg<TDecorator<T>>
+ : public NYT::TFormatArgBase
+{
+ static constexpr auto ConversionSpecifiers = TFormatArg<T>::ConversionSpecifiers;
+ static constexpr auto FlagSpecifiers =
+ TFormatArgBase::ExtendConversion</*Hot*/ true, 1, std::array{'D'}, /*TFrom*/ T>::ExtendConversion();
+};
+
+template <class T>
+void FormatValue(NYT::TStringBuilderBase* builder, const TDecorator<T>& value, TStringBuf spec)
+{
+ bool append = (spec[0] == 'D');
+
+ if (append) {
+ builder->AppendString("TDecorator value: ");
+ FormatValue(builder, value.Value, TStringBuf(&spec[1], spec.size() - 1));
+ return;
+ }
+
+ FormatValue(builder, value.Value, spec);
+}
+
+Format("Testing: %v", TDecorator{TMyPair{42, "Hello"}});
+// Testing: {42, Hello}
+Format("Testing: %Dv", TDecorator{TMyPair{42, "Hello"}});
+// Testing: TDecorator value: {42, Hello}
+Format("Testing: %Dkv", TDecorator{TMyPair{42, "Hello"}});
+// Testing: TDecorator value: 42_Hello
+```
+
+### ToString auto generation
+
+For names inside namespaces enclosing `NYT` and `std` we automatically generate overload of `ToString` which uses `FormatValue` function if there is such a function. In examples below we assume that `CFormattable` holds true for each type:
+```cpp
+auto val = ToString(NYT::TMyPair{42, "Hello"}}); // Works since TMyPair comes from namespace NYT;
+
+auto val = ToString(std::optional{NYT::TMyPair{42, "Hello"}}}); // Works since optional comes from namespace std
+
+auto val = ToString(NYT::NOrm::::NClient::NObjects::TObjectKey{}); // Works since NOrm::::NClient::NObjects enclose namespace NYT
+
+auto val = ToString(NMyNs::TMyType{}); // Falls back to util ToString because NMyNs encloses neither std nor NYT. Fate is unknown.
+
+auto val = ToString(NMyNS::TMyContainer<NYT::TMyPair>{}); // Falls back to util ToString because NMyNs encloses neither std nor NYT (we don't care about template parameters). Fate is unknown.
+
+auto val = NYT::ToString(NMyNs::TMyType{}); // Works.
+
+auto val = NYT::ToString(NMyNS::TMyContainer<NYT::TMyPair>{}); // Also works.
+
+{
+ using ::ToString; // Irrelevant since NYT::ToString is more constrained.
+ using NYT::ToString;
+ auto val = ToString(NMyNS::TMyContainer<NYT::TMyPair>{}); // Also works.
+}
+```
+
+### Implementing `FormatValue` via `ToString`
+
+One thing you may attempt to do is to use already defined `ToString` method to implement `FormatValue`. There are two cases for this:
+ 1. You have an overload of `ToString` visible from the inside of `FormatValue` which is not the util default overload. In this case you are fine.
+ 2. You rely on util `ToString` overload. In this case you hit an infinite recursion loop if name of your type comes from `std` or `NYT` namespaces. We strongly recommend that you stop relying on util `ToString` and simply write `FormatValue` from scratch. Should this be impossible, use `NYT::ToStringIgnoringFormatValue(const T&)` which implements default `ToString` mechanism (via `operator <<`) from util. This method has a different name hence it will break the recursion loop.
+
+### Format extensions.
+
+There are some types from util and other cpp libraries which one might want to print, but we don't need them in the yt project (allegedly). Some of these dependencies are located in [library/cpp/yt/string/format_extensions]. If you don't care about granularity, simply include "library/cpp/yt/string/format_extensions/all.h" to enable support for every currently known dependency.
diff --git a/library/cpp/yt/string/string_builder-inl.h b/library/cpp/yt/string/string_builder-inl.h
index 683a241c9cc..9fb3ae94f86 100644
--- a/library/cpp/yt/string/string_builder-inl.h
+++ b/library/cpp/yt/string/string_builder-inl.h
@@ -118,7 +118,7 @@ inline void TStringBuilder::DoReserve(size_t newLength)
////////////////////////////////////////////////////////////////////////////////
-inline void FormatValue(TStringBuilderBase* builder, const TStringBuilder& value, TStringBuf /*format*/)
+inline void FormatValue(TStringBuilderBase* builder, const TStringBuilder& value, TStringBuf /*spec*/)
{
builder->AppendString(value.GetBuffer());
}
@@ -165,14 +165,17 @@ TString ToStringIgnoringFormatValue(const T& t)
#include <util/string/cast.h>
-// util/string/cast.h extension for yt types only
+// util/string/cast.h extension for yt and std types only
// TODO(arkady-e1ppa): Abolish ::ToString in
// favour of either NYT::ToString or
// automatic formatting wherever it is needed.
namespace NPrivate {
template <class T>
- requires (NYT::NDetail::CYtName<T> && NYT::CFormattable<T>)
+ requires (
+ (NYT::NDetail::IsNYTName<T>() ||
+ NYT::NDetail::IsStdName<T>()) &&
+ NYT::CFormattable<T>)
struct TToString<T, false>
{
static TString Cvt(const T& t)
diff --git a/library/cpp/yt/string/string_builder.h b/library/cpp/yt/string/string_builder.h
index 122e5d19a0a..4cba45bc725 100644
--- a/library/cpp/yt/string/string_builder.h
+++ b/library/cpp/yt/string/string_builder.h
@@ -14,9 +14,7 @@ class TStringBuilder;
class TDelimitedStringBuilderWrapper;
template <class... TArgs>
-void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args);
-template <class... TArgs>
-void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args);
+void Format(TStringBuilderBase* builder, TFormatString<TArgs...> fmt, TArgs&&... args);
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/string/unittests/format_ut.cpp b/library/cpp/yt/string/unittests/format_ut.cpp
index 14b783516c9..4083338c44a 100644
--- a/library/cpp/yt/string/unittests/format_ut.cpp
+++ b/library/cpp/yt/string/unittests/format_ut.cpp
@@ -217,6 +217,12 @@ TEST(TFormatTest, VectorArg)
EXPECT_EQ(FormatVector("a is %v, b is %v, c is %v", params), "a is a, b is b, c is c");
}
+TEST(TFormatTest, RuntimeFormat)
+{
+ TString format = "Hello %v";
+ EXPECT_EQ(Format(TRuntimeFormat(format), "World"), "Hello World");
+}
+
////////////////////////////////////////////////////////////////////////////////
} // namespace
diff --git a/library/cpp/yt/string/ya.make b/library/cpp/yt/string/ya.make
index 95b07ee0d1f..db3a477b358 100644
--- a/library/cpp/yt/string/ya.make
+++ b/library/cpp/yt/string/ya.make
@@ -6,6 +6,7 @@ SRCS(
enum.cpp
guid.cpp
string.cpp
+ format_string.cpp
format.cpp
)
diff --git a/library/cpp/yt/threading/fork_aware_spin_lock.h b/library/cpp/yt/threading/fork_aware_spin_lock.h
index 215fb58dc91..a07a5f1e52a 100644
--- a/library/cpp/yt/threading/fork_aware_spin_lock.h
+++ b/library/cpp/yt/threading/fork_aware_spin_lock.h
@@ -1,8 +1,8 @@
#pragma once
#include "public.h"
+#include "spin_lock.h"
-#include <util/system/spinlock.h>
#include <util/system/src_location.h>
namespace NYT::NThreading {
@@ -30,7 +30,7 @@ public:
bool IsLocked() const noexcept;
private:
- ::TSpinLock SpinLock_;
+ ::NYT::NThreading::TSpinLock SpinLock_;
};
////////////////////////////////////////////////////////////////////////////////