diff options
66 files changed, 1295 insertions, 869 deletions
diff --git a/library/cpp/yt/logging/logger-inl.h b/library/cpp/yt/logging/logger-inl.h index b20526532d5..a334a371421 100644 --- a/library/cpp/yt/logging/logger-inl.h +++ b/library/cpp/yt/logging/logger-inl.h @@ -24,7 +24,7 @@ inline bool TLogger::IsAnchorUpToDate(const TLoggingAnchor& position) const template <class... TArgs> void TLogger::AddTag(const char* format, TArgs&&... args) { - AddRawTag(Format(format, std::forward<TArgs>(args)...)); + AddRawTag(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...)); } template <class TType> diff --git a/library/cpp/yt/logging/logger.h b/library/cpp/yt/logging/logger.h index c4a4e6adefd..5cbd6818765 100644 --- a/library/cpp/yt/logging/logger.h +++ b/library/cpp/yt/logging/logger.h @@ -22,7 +22,7 @@ #include <atomic> -#if (__clang__ || __clang_major__ < 16) +#if (!__clang__ || __clang_major__ < 16) #define YT_DISABLE_FORMAT_STATIC_ANALYSIS #endif diff --git a/library/cpp/yt/logging/static_analysis-inl.h b/library/cpp/yt/logging/static_analysis-inl.h index d4ec5343bc1..13cd425d581 100644 --- a/library/cpp/yt/logging/static_analysis-inl.h +++ b/library/cpp/yt/logging/static_analysis-inl.h @@ -6,7 +6,7 @@ #include <library/cpp/yt/misc/preprocessor.h> -#include <library/cpp/yt/string/format_analyser.h> +#include <library/cpp/yt/string/format.h> #include <string_view> #include <variant> // monostate @@ -20,6 +20,12 @@ 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 @@ -32,7 +38,8 @@ struct TLoggerFormatArgs // Used for macro conversion. Purposefully undefined. template <class... TArgs> -TLoggerFormatArgs<TArgs...> AsFormatArgs(TArgs&&...); +TLoggerFormatArgs<std::remove_cvref_t<TArgs>...> +AsFormatArgs(TArgs&&...); //////////////////////////////////////////////////////////////////////////////// @@ -135,5 +142,5 @@ struct NYT::TFormatArg<NYT::NLogging::NDetail::TLoggerFormatArg<T>> // "\"Value: %\" \"u\"" // Thus adding a \" \" sequence. static constexpr auto FlagSpecifiers - = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ NYT::TFormatArg<T>>(); + = TFormatArgBase::ExtendFlags</*Hot*/ false, 2, std::array{'\"', ' '}, /*TFrom*/ T>(); }; diff --git a/library/cpp/yt/memory/ref.cpp b/library/cpp/yt/memory/ref.cpp index b5b636c9db0..c01094846ee 100644 --- a/library/cpp/yt/memory/ref.cpp +++ b/library/cpp/yt/memory/ref.cpp @@ -6,6 +6,8 @@ #include <library/cpp/yt/misc/port.h> +#include <library/cpp/yt/string/format.h> + #include <util/system/info.h> #include <util/system/align.h> @@ -304,24 +306,24 @@ TSharedMutableRef TSharedMutableRef::MakeCopy(TRef ref, TRefCountedTypeCookie ta //////////////////////////////////////////////////////////////////////////////// -TString ToString(TRef ref) +void FormatValue(TStringBuilderBase* builder, const TRef& ref, TStringBuf spec) { - return TString(ref.Begin(), ref.End()); + FormatValue(builder, TStringBuf{ref.Begin(), ref.End()}, spec); } -TString ToString(const TMutableRef& ref) +void FormatValue(TStringBuilderBase* builder, const TMutableRef& ref, TStringBuf spec) { - return ToString(TRef(ref)); + FormatValue(builder, TRef(ref), spec); } -TString ToString(const TSharedRef& ref) +void FormatValue(TStringBuilderBase* builder, const TSharedRef& ref, TStringBuf spec) { - return ToString(TRef(ref)); + FormatValue(builder, TRef(ref), spec); } -TString ToString(const TSharedMutableRef& ref) +void FormatValue(TStringBuilderBase* builder, const TSharedMutableRef& ref, TStringBuf spec) { - return ToString(TRef(ref)); + FormatValue(builder, TRef(ref), spec); } size_t GetPageSize() diff --git a/library/cpp/yt/memory/ref.h b/library/cpp/yt/memory/ref.h index 2177778a8f3..2dfe861e759 100644 --- a/library/cpp/yt/memory/ref.h +++ b/library/cpp/yt/memory/ref.h @@ -4,6 +4,8 @@ #include "range.h" #include "shared_range.h" +#include <library/cpp/yt/string/format.h> + #include <type_traits> namespace NYT { @@ -381,10 +383,10 @@ private: //////////////////////////////////////////////////////////////////////////////// -TString ToString(TRef ref); -TString ToString(const TMutableRef& ref); -TString ToString(const TSharedRef& ref); -TString ToString(const TSharedMutableRef& ref); +void FormatValue(TStringBuilderBase* builder, const TRef& ref, TStringBuf spec); +void FormatValue(TStringBuilderBase* builder, const TMutableRef& ref, TStringBuf spec); +void FormatValue(TStringBuilderBase* builder, const TSharedRef& ref, TStringBuf spec); +void FormatValue(TStringBuilderBase* builder, const TSharedMutableRef& ref, TStringBuf); size_t GetPageSize(); size_t RoundUpToPage(size_t bytes); diff --git a/library/cpp/yt/misc/wrapper_traits.h b/library/cpp/yt/misc/wrapper_traits.h index 8a6d5814199..1e000ebaffb 100644 --- a/library/cpp/yt/misc/wrapper_traits.h +++ b/library/cpp/yt/misc/wrapper_traits.h @@ -1,5 +1,7 @@ #pragma once +#include <util/generic/strbuf.h> + #include <concepts> #include <utility> diff --git a/library/cpp/yt/string/format-inl.h b/library/cpp/yt/string/format-inl.h index 4d9634039c1..069538d84bf 100644 --- a/library/cpp/yt/string/format-inl.h +++ b/library/cpp/yt/string/format-inl.h @@ -13,7 +13,11 @@ #include <library/cpp/yt/containers/enum_indexed_array.h> +#include <library/cpp/yt/misc/concepts.h> #include <library/cpp/yt/misc/enum.h> +#include <library/cpp/yt/misc/wrapper_traits.h> + +#include <util/generic/maybe.h> #include <util/system/platform.h> @@ -21,28 +25,241 @@ #include <optional> #include <span> +#if __cplusplus >= 202302L + #include <filesystem> +#endif + namespace NYT { //////////////////////////////////////////////////////////////////////////////// -static constexpr char GenericSpecSymbol = 'v'; +// Helper functions for formatting. +namespace NDetail { + +constexpr inline char IntroductorySymbol = '%'; +constexpr inline char GenericSpecSymbol = 'v'; inline bool IsQuotationSpecSymbol(char symbol) { return symbol == 'Q' || symbol == 'q'; } +//////////////////////////////////////////////////////////////////////////////// + +template <class TValue> +void FormatValueViaSprintf( + TStringBuilderBase* builder, + TValue value, + TStringBuf spec, + TStringBuf genericSpec); + +template <class TValue> +void FormatIntValue( + TStringBuilderBase* builder, + TValue value, + TStringBuf spec, + TStringBuf genericSpec); + +void FormatPointerValue( + TStringBuilderBase* builder, + const void* value, + TStringBuf spec); + +//////////////////////////////////////////////////////////////////////////////// + +// Helper concepts for matching the correct overload. +// NB(arkady-e1ppa): We prefer to hardcode the known types +// so that someone doesn't accidentally implement the +// "SimpleRange" concept and have a non-trivial +// formatting procedure at the same time. + +template <class R> +concept CKnownRange = + requires (R r) { [] <class... Ts> (std::vector<Ts...>) { } (r); } || + requires (R r) { [] <class T, size_t E> (std::span<T, E>) { } (r); } || + requires (R r) { [] <class T, size_t N> (TCompactVector<T, N>) { } (r); } || + requires (R r) { [] <class... Ts> (std::set<Ts...>) { } (r); } || + requires (R r) { [] <class... Ts> (THashSet<Ts...>) { } (r); } || + requires (R r) { [] <class... Ts> (THashMultiSet<Ts...>) { } (r); }; + +//////////////////////////////////////////////////////////////////////////////// + +template <class R> +concept CKnownKVRange = + requires (R r) { [] <class... Ts> (std::map<Ts...>) { } (r); } || + requires (R r) { [] <class... Ts> (std::multimap<Ts...>) { } (r); } || + requires (R r) { [] <class... Ts> (THashMap<Ts...>) { } (r); } || + requires (R r) { [] <class... Ts> (THashMultiMap<Ts...>) { } (r); }; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +template <class TRange, class TFormatter> +void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('['); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item); + ++index; + } + builder->AppendChar(']'); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class TRange, class TFormatter> +void FormatKeyValueRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +{ + builder->AppendChar('{'); + size_t index = 0; + for (const auto& item : range) { + if (index > 0) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + if (index == limit) { + builder->AppendString(DefaultRangeEllipsisFormat); + break; + } + formatter(builder, item.first); + builder->AppendString(DefaultKeyValueDelimiter); + formatter(builder, item.second); + ++index; + } + builder->AppendChar('}'); +} + +//////////////////////////////////////////////////////////////////////////////// + +template <class R> +concept CFormattableRange = + NDetail::CKnownRange<R> && + CFormattable<typename R::value_type>; + +template <class R> +concept CFormattableKVRange = + NDetail::CKnownKVRange<R> && + CFormattable<typename R::key_type> && + CFormattable<typename R::value_type>; + +//////////////////////////////////////////////////////////////////////////////// + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TBegin TFormattableView<TRange, TFormatter>::begin() const +{ + return RangeBegin; +} + +template <class TRange, class TFormatter> +typename TFormattableView<TRange, TFormatter>::TEnd TFormattableView<TRange, TFormatter>::end() const +{ + return RangeEnd; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeFormattableView( + const TRange& range, + TFormatter&& formatter) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter)}; +} + +template <class TRange, class TFormatter> +TFormattableView<TRange, TFormatter> MakeShrunkFormattableView( + const TRange& range, + TFormatter&& formatter, + size_t limit) +{ + return TFormattableView<TRange, std::decay_t<TFormatter>>{ + range.begin(), + range.end(), + std::forward<TFormatter>(formatter), + limit}; +} + +template <class TFormatter> +TFormatterWrapper<TFormatter> MakeFormatterWrapper( + TFormatter&& formatter) +{ + return TFormatterWrapper<TFormatter>{ + .Formatter = std::move(formatter) + }; +} + +template <class... TArgs> +TLazyMultiValueFormatter<TArgs...>::TLazyMultiValueFormatter( + TStringBuf format, + TArgs&&... args) + : Format_(format) + , Args_(std::forward<TArgs>(args)...) +{ } + +template <class... TArgs> +auto MakeLazyMultiValueFormatter(TStringBuf format, TArgs&&... args) +{ + return TLazyMultiValueFormatter<TArgs...>(format, std::forward<TArgs>(args)...); +} + +//////////////////////////////////////////////////////////////////////////////// + +// Non-container objects. + +#define XX(valueType, castType, genericSpec) \ + inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf spec) \ + { \ + NYT::NDetail::FormatIntValue(builder, static_cast<castType>(value), spec, genericSpec); \ + } + +XX(i8, i32, TStringBuf("d")) +XX(ui8, ui32, TStringBuf("u")) +XX(i16, i32, TStringBuf("d")) +XX(ui16, ui32, TStringBuf("u")) +XX(i32, i32, TStringBuf("d")) +XX(ui32, ui32, TStringBuf("u")) +XX(long, i64, TStringBuf(PRIdLEAST64)) +XX(long long, i64, TStringBuf(PRIdLEAST64)) +XX(unsigned long, ui64, TStringBuf(PRIuLEAST64)) +XX(unsigned long long, ui64, TStringBuf(PRIuLEAST64)) + +#undef XX + +#define XX(valueType, castType, genericSpec) \ + inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf spec) \ + { \ + NYT::NDetail::FormatValueViaSprintf(builder, static_cast<castType>(value), spec, genericSpec); \ + } + +XX(double, double, TStringBuf("lf")) +XX(float, float, TStringBuf("f")) + +#undef XX + +// Pointer +template <class T> +void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf spec) +{ + NYT::NDetail::FormatPointerValue(builder, static_cast<const void*>(value), spec); +} + // TStringBuf -inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBuf spec) { - if (!format) { + if (!spec) { builder->AppendString(value); return; } // Parse alignment. bool alignLeft = false; - const char* current = format.begin(); + const char* current = spec.begin(); if (*current == '-') { alignLeft = true; ++current; @@ -75,7 +292,7 @@ inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBu bool singleQuotes = false; bool doubleQuotes = false; bool escape = false; - while (current < format.end()) { + while (current < spec.end()) { switch (*current++) { case 'q': singleQuotes = true; @@ -123,40 +340,66 @@ inline void FormatValue(TStringBuilderBase* builder, TStringBuf value, TStringBu } // TString -inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, const TString& value, TStringBuf spec) { - FormatValue(builder, TStringBuf(value), format); + FormatValue(builder, TStringBuf(value), spec); } // const char* -inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, const char* value, TStringBuf spec) { - FormatValue(builder, TStringBuf(value), format); + FormatValue(builder, TStringBuf(value), spec); +} + +template <size_t N> +inline void FormatValue(TStringBuilderBase* builder, const char (&value)[N], TStringBuf spec) +{ + FormatValue(builder, TStringBuf(value), spec); } // char* -inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, char* value, TStringBuf spec) { - FormatValue(builder, TStringBuf(value), format); + FormatValue(builder, TStringBuf(value), spec); } +// std::string +inline void FormatValue(TStringBuilderBase* builder, const std::string& value, TStringBuf spec) +{ + FormatValue(builder, TStringBuf(value), spec); +} + +// std::string_view +inline void FormatValue(TStringBuilderBase* builder, const std::string_view& value, TStringBuf spec) +{ + FormatValue(builder, TStringBuf(value), spec); +} + +#if __cplusplus >= 202302L +// std::filesystem::path +inline void FormatValue(TStringBuilderBase* builder, const std::filesystem::path& value, TStringBuf spec) +{ + FormatValue(builder, std::string(value), spec); +} +#endif + // char -inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, char value, TStringBuf spec) { - FormatValue(builder, TStringBuf(&value, 1), format); + FormatValue(builder, TStringBuf(&value, 1), spec); } // bool -inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf format) +inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf spec) { // Parse custom flags. bool lowercase = false; - const char* current = format.begin(); - while (current != format.end()) { + const char* current = spec.begin(); + while (current != spec.end()) { if (*current == 'l') { ++current; lowercase = true; - } else if (IsQuotationSpecSymbol(*current)) { + } else if (NYT::NDetail::IsQuotationSpecSymbol(*current)) { ++current; } else break; @@ -169,399 +412,316 @@ inline void FormatValue(TStringBuilderBase* builder, bool value, TStringBuf form builder->AppendString(str); } -// Fallback to ToString -struct TToStringFallbackValueFormatterTag -{ }; +// TDuration +inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*spec*/) +{ + builder->AppendFormat("%vus", value.MicroSeconds()); +} -template <class TValue, class = void> -struct TValueFormatter +// TInstant +inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf spec) { - static TToStringFallbackValueFormatterTag Do(TStringBuilderBase* builder, const TValue& value, TStringBuf format) - { - using ::ToString; - FormatValue(builder, ToString(value), format); - return {}; - } -}; + // TODO(babenko): Optimize. + FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec); +} // Enum template <class TEnum> -struct TValueFormatter<TEnum, typename std::enable_if<TEnumTraits<TEnum>::IsEnum>::type> + requires (TEnumTraits<TEnum>::IsEnum) +void FormatValue(TStringBuilderBase* builder, TEnum value, TStringBuf spec) { - static void Do(TStringBuilderBase* builder, TEnum value, TStringBuf format) - { - // Parse custom flags. - bool lowercase = false; - const char* current = format.begin(); - while (current != format.end()) { - if (*current == 'l') { - ++current; - lowercase = true; - } else if (IsQuotationSpecSymbol(*current)) { - ++current; - } else { - break; - } + // Parse custom flags. + bool lowercase = false; + const char* current = spec.begin(); + while (current != spec.end()) { + if (*current == 'l') { + ++current; + lowercase = true; + } else if (NYT::NDetail::IsQuotationSpecSymbol(*current)) { + ++current; + } else { + break; } - - FormatEnum(builder, value, lowercase); } -}; -template <class TRange, class TFormatter> -typename TFormattableView<TRange, TFormatter>::TBegin TFormattableView<TRange, TFormatter>::begin() const -{ - return RangeBegin; + FormatEnum(builder, value, lowercase); } -template <class TRange, class TFormatter> -typename TFormattableView<TRange, TFormatter>::TEnd TFormattableView<TRange, TFormatter>::end() const +template <class TArcadiaEnum> + requires (std::is_enum_v<TArcadiaEnum> && !TEnumTraits<TArcadiaEnum>::IsEnum) +void FormatValue(TStringBuilderBase* builder, TArcadiaEnum value, TStringBuf /*spec*/) { - return RangeEnd; + // NB(arkady-e1ppa): This can catch normal enums which + // just want to be serialized as numbers. + // Unfortunately, we have no way of determining that other than + // marking every relevant arcadia enum in the code by trait + // or writing their complete trait and placing such trait in + // every single file where it is formatted. + // We gotta figure something out but until that + // we will just have to make a string for such enums. + // If only arcadia enums provided compile-time check + // if enum is serializable :(((((. + builder->AppendString(NYT::ToStringIgnoringFormatValue(value)); } +// Container objects. +// NB(arkady-e1ppa): In order to support container combinations +// we forward-declare them before defining. + +// TMaybe +template <class T, class TPolicy> +void FormatValue(TStringBuilderBase* builder, const TMaybe<T, TPolicy>& value, TStringBuf spec); + +// std::optional +template <class T> +void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec); + +// std::pair +template <class A, class B> +void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec); + +// std::tuple +template <class... Ts> +void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec); + +// TEnumIndexedArray +template <class E, class T> +void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec); + +// One-valued ranges +template <CFormattableRange TRange> +void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec); + +// Two-valued ranges +template <CFormattableKVRange TRange> +void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf spec); + +// FormattableView template <class TRange, class TFormatter> -TFormattableView<TRange, TFormatter> MakeFormattableView( - const TRange& range, - TFormatter&& formatter) +void FormatValue( + TStringBuilderBase* builder, + const TFormattableView<TRange, TFormatter>& formattableView, + TStringBuf spec); + +// TFormatterWrapper +template <class TFormatter> +void FormatValue( + TStringBuilderBase* builder, + const TFormatterWrapper<TFormatter>& wrapper, + TStringBuf spec); + +// TLazyMultiValueFormatter +template <class... TArgs> +void FormatValue( + TStringBuilderBase* builder, + const TLazyMultiValueFormatter<TArgs...>& value, + TStringBuf /*spec*/); + +// TMaybe +template <class T, class TPolicy> +void FormatValue(TStringBuilderBase* builder, const TMaybe<T, TPolicy>& value, TStringBuf spec) { - return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter)}; + FormatValue(builder, NYT::ToStringIgnoringFormatValue(value), spec); } -template <class TRange, class TFormatter> -TFormattableView<TRange, TFormatter> MakeShrunkFormattableView( - const TRange& range, - TFormatter&& formatter, - size_t limit) +// std::optional: nullopt +inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*spec*/) { - return TFormattableView<TRange, std::decay_t<TFormatter>>{range.begin(), range.end(), std::forward<TFormatter>(formatter), limit}; + builder->AppendString(TStringBuf("<null>")); } -template <class TRange, class TFormatter> -void FormatRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +// std::optional: generic T +template <class T> +void FormatValue(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf spec) { - builder->AppendChar('['); - size_t index = 0; - for (const auto& item : range) { - if (index > 0) { - builder->AppendString(DefaultJoinToStringDelimiter); - } - if (index == limit) { - builder->AppendString(DefaultRangeEllipsisFormat); - break; - } - formatter(builder, item); - ++index; + if (value.has_value()) { + FormatValue(builder, *value, spec); + } else { + FormatValue(builder, std::nullopt, spec); } - builder->AppendChar(']'); } -template <class TRange, class TFormatter> -void FormatKeyValueRange(TStringBuilderBase* builder, const TRange& range, const TFormatter& formatter, size_t limit = std::numeric_limits<size_t>::max()) +// std::pair +template <class A, class B> +void FormatValue(TStringBuilderBase* builder, const std::pair<A, B>& value, TStringBuf spec) { builder->AppendChar('{'); - size_t index = 0; - for (const auto& item : range) { - if (index > 0) { - builder->AppendString(DefaultJoinToStringDelimiter); - } - if (index == limit) { - builder->AppendString(DefaultRangeEllipsisFormat); - break; - } - formatter(builder, item.first); - builder->AppendString(DefaultKeyValueDelimiter); - formatter(builder, item.second); - ++index; - } + FormatValue(builder, value.first, spec); + builder->AppendString(TStringBuf(", ")); + FormatValue(builder, value.second, spec); builder->AppendChar('}'); } -// TFormattableView -template <class TRange, class TFormatter> -struct TValueFormatter<TFormattableView<TRange, TFormatter>> +// std::tuple +template <class... Ts> +void FormatValue(TStringBuilderBase* builder, const std::tuple<Ts...>& value, TStringBuf spec) { - static void Do(TStringBuilderBase* builder, const TFormattableView<TRange, TFormatter>& range, TStringBuf /*format*/) - { - FormatRange(builder, range, range.Formatter, range.Limit); - } -}; + builder->AppendChar('{'); -template <class TFormatter> -TFormatterWrapper<TFormatter> MakeFormatterWrapper( - TFormatter&& formatter) -{ - return TFormatterWrapper<TFormatter>{ - .Formatter = std::move(formatter) - }; + [&] <size_t... Idx> (std::index_sequence<Idx...>) { + ([&] { + FormatValue(builder, std::get<Idx>(value), spec); + if constexpr (Idx != sizeof...(Ts)) { + builder->AppendString(TStringBuf(", ")); + } + } (), ...); + } (std::index_sequence_for<Ts...>()); + + builder->AppendChar('}'); } -// TFormatterWrapper -template <class TFormatter> -struct TValueFormatter<TFormatterWrapper<TFormatter>> +// TEnumIndexedArray +template <class E, class T> +void FormatValue(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf spec) { - static void Do(TStringBuilderBase* builder, const TFormatterWrapper<TFormatter>& wrapper, TStringBuf /*format*/) - { - wrapper.Formatter(builder); + builder->AppendChar('{'); + bool firstItem = true; + for (const auto& index : TEnumTraits<E>::GetDomainValues()) { + if (!firstItem) { + builder->AppendString(DefaultJoinToStringDelimiter); + } + FormatValue(builder, index, spec); + builder->AppendString(": "); + FormatValue(builder, collection[index], spec); + firstItem = false; } -}; + builder->AppendChar('}'); +} -// std::vector -template <class T, class TAllocator> -struct TValueFormatter<std::vector<T, TAllocator>> +// One-valued ranges +template <CFormattableRange TRange> +void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf /*spec*/) { - static void Do(TStringBuilderBase* builder, const std::vector<T, TAllocator>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} -// std::span -template <class T, size_t Extent> -struct TValueFormatter<std::span<T, Extent>> +// Two-valued ranges +template <CFormattableKVRange TRange> +void FormatValue(TStringBuilderBase* builder, const TRange& collection, TStringBuf /*spec*/) { - static void Do(TStringBuilderBase* builder, const std::span<T, Extent>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; + NYT::FormatKeyValueRange(builder, collection, TDefaultFormatter()); +} -// TCompactVector -template <class T, unsigned N> -struct TValueFormatter<TCompactVector<T, N>> +// FormattableView +template <class TRange, class TFormatter> +void FormatValue( + TStringBuilderBase* builder, + const TFormattableView<TRange, TFormatter>& formattableView, + TStringBuf /*spec*/) { - static void Do(TStringBuilderBase* builder, const TCompactVector<T, N>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; + NYT::FormatRange(builder, formattableView, formattableView.Formatter, formattableView.Limit); +} -// std::set -template <class T> -struct TValueFormatter<std::set<T>> +// TFormatterWrapper +template <class TFormatter> +void FormatValue( + TStringBuilderBase* builder, + const TFormatterWrapper<TFormatter>& wrapper, + TStringBuf /*spec*/) { - static void Do(TStringBuilderBase* builder, const std::set<T>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; + wrapper.Formatter(builder); +} -// std::map -template <class K, class V> -struct TValueFormatter<std::map<K, V>> +// TLazyMultiValueFormatter +template <class... TArgs> +void FormatValue( + TStringBuilderBase* builder, + const TLazyMultiValueFormatter<TArgs...>& value, + TStringBuf /*spec*/) { - static void Do(TStringBuilderBase* builder, const std::map<K, V>& collection, TStringBuf /*format*/) - { - FormatKeyValueRange(builder, collection, TDefaultFormatter()); - } -}; + std::apply( + [&] <class... TInnerArgs> (TInnerArgs&&... args) { + builder->AppendFormat(value.Format_, std::forward<TInnerArgs>(args)...); + }, + value.Args_); +} -// std::multimap -template <class K, class V> -struct TValueFormatter<std::multimap<K, V>> -{ - static void Do(TStringBuilderBase* builder, const std::multimap<K, V>& collection, TStringBuf /*format*/) - { - FormatKeyValueRange(builder, collection, TDefaultFormatter()); - } -}; +//////////////////////////////////////////////////////////////////////////////// -// THashSet -template <class T> -struct TValueFormatter<THashSet<T>> -{ - static void Do(TStringBuilderBase* builder, const THashSet<T>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; +namespace NDetail { -// THashMultiSet -template <class T> -struct TValueFormatter<THashMultiSet<T>> -{ - static void Do(TStringBuilderBase* builder, const THashMultiSet<T>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; +template <size_t HeadPos, class... TArgs> +class TValueFormatter; -// THashMap -template <class K, class V> -struct TValueFormatter<THashMap<K, V>> +template <size_t HeadPos> +class TValueFormatter<HeadPos> { - static void Do(TStringBuilderBase* builder, const THashMap<K, V>& collection, TStringBuf /*format*/) +public: + void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*spec*/) const { - FormatKeyValueRange(builder, collection, TDefaultFormatter()); + builder->AppendString(TStringBuf("<missing argument>")); } }; -// THashMultiMap -template <class K, class V> -struct TValueFormatter<THashMultiMap<K, V>> +template <size_t HeadPos, class THead, class... TTail> +class TValueFormatter<HeadPos, THead, TTail...> { - static void Do(TStringBuilderBase* builder, const THashMultiMap<K, V>& collection, TStringBuf /*format*/) - { - FormatKeyValueRange(builder, collection, TDefaultFormatter()); - } -}; +public: + explicit TValueFormatter(const THead& head, const TTail&... tail) noexcept + : Head_(head) + , TailFormatter_(tail...) + { } -// TEnumIndexedArray -template <class E, class T> -struct TValueFormatter<TEnumIndexedArray<E, T>> -{ - static void Do(TStringBuilderBase* builder, const TEnumIndexedArray<E, T>& collection, TStringBuf format) + void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const { - builder->AppendChar('{'); - bool firstItem = true; - for (const auto& index : TEnumTraits<E>::GetDomainValues()) { - if (!firstItem) { - builder->AppendString(DefaultJoinToStringDelimiter); - } - FormatValue(builder, index, format); - builder->AppendString(": "); - FormatValue(builder, collection[index], format); - firstItem = false; + YT_ASSERT(index >= HeadPos); + if (index == HeadPos) { + FormatValue(builder, Head_, spec); + } else { + TailFormatter_(index, builder, spec); } - builder->AppendChar('}'); } -}; -// std::pair -template <class T1, class T2> -struct TValueFormatter<std::pair<T1, T2>> -{ - static void Do(TStringBuilderBase* builder, const std::pair<T1, T2>& value, TStringBuf format) - { - builder->AppendChar('{'); - FormatValue(builder, value.first, format); - builder->AppendString(TStringBuf(", ")); - FormatValue(builder, value.second, format); - builder->AppendChar('}'); - } +private: + const THead& Head_; + TValueFormatter<HeadPos + 1, TTail...> TailFormatter_; }; -// std::optional -inline void FormatValue(TStringBuilderBase* builder, std::nullopt_t, TStringBuf /*format*/) -{ - builder->AppendString(TStringBuf("<null>")); -} +//////////////////////////////////////////////////////////////////////////////// -template <class T> -struct TValueFormatter<std::optional<T>> +template <class TRangeValue> +class TRangeFormatter { - static void Do(TStringBuilderBase* builder, const std::optional<T>& value, TStringBuf format) +public: + template <class... TArgs> + requires std::constructible_from<std::span<const TRangeValue>, TArgs...> + explicit TRangeFormatter(TArgs&&... args) noexcept + : Span_(std::forward<TArgs>(args)...) + { } + + void operator() (size_t index, TStringBuilderBase* builder, TStringBuf spec) const { - if (value) { - FormatValue(builder, *value, format); + if (index >= Span_.size()) { + builder->AppendString(TStringBuf("<missing argument>")); } else { - FormatValue(builder, std::nullopt, format); + FormatValue(builder, *(Span_.begin() + index), spec); } } -}; - -template <class TValue> -auto FormatValue(TStringBuilderBase* builder, const TValue& value, TStringBuf format) -> - decltype(TValueFormatter<TValue>::Do(builder, value, format)) -{ - return TValueFormatter<TValue>::Do(builder, value, format); -} - -namespace NDetail { - -template <class TValue> -void FormatValueViaSprintf( - TStringBuilderBase* builder, - TValue value, - TStringBuf format, - TStringBuf genericSpec); - -template <class TValue> -void FormatIntValue( - TStringBuilderBase* builder, - TValue value, - TStringBuf format, - TStringBuf genericSpec); -void FormatPointerValue( - TStringBuilderBase* builder, - const void* value, - TStringBuf format); - -} // namespace NDetail - -#define XX(valueType, castType, genericSpec) \ - inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ - { \ - NYT::NDetail::FormatIntValue(builder, static_cast<castType>(value), format, genericSpec); \ - } - -XX(i8, i32, TStringBuf("d")) -XX(ui8, ui32, TStringBuf("u")) -XX(i16, i32, TStringBuf("d")) -XX(ui16, ui32, TStringBuf("u")) -XX(i32, i32, TStringBuf("d")) -XX(ui32, ui32, TStringBuf("u")) -#ifdef _win_ -XX(long long, i64, TStringBuf("lld")) -XX(unsigned long long, ui64, TStringBuf("llu")) -#else -XX(long, i64, TStringBuf("ld")) -XX(unsigned long, ui64, TStringBuf("lu")) -#endif - -#undef XX - -#define XX(valueType, castType, genericSpec) \ - inline void FormatValue(TStringBuilderBase* builder, valueType value, TStringBuf format) \ - { \ - NYT::NDetail::FormatValueViaSprintf(builder, static_cast<castType>(value), format, genericSpec); \ - } - -XX(double, double, TStringBuf("lf")) -XX(float, float, TStringBuf("f")) +private: + std::span<const TRangeValue> Span_; +}; -#undef XX +//////////////////////////////////////////////////////////////////////////////// -// Pointer template <class T> -void FormatValue(TStringBuilderBase* builder, T* value, TStringBuf format) -{ - NYT::NDetail::FormatPointerValue(builder, static_cast<const void*>(value), format); -} - -// TDuration (specialize for performance reasons) -inline void FormatValue(TStringBuilderBase* builder, TDuration value, TStringBuf /*format*/) -{ - builder->AppendFormat("%vus", value.MicroSeconds()); -} - -// TInstant (specialize for TFormatTraits) -inline void FormatValue(TStringBuilderBase* builder, TInstant value, TStringBuf format) -{ - // TODO(babenko): optimize - builder->AppendFormat("%v", ToString(value), format); -} +concept CFormatter = CInvocable<T, void(size_t, TStringBuilderBase*, TStringBuf)>; //////////////////////////////////////////////////////////////////////////////// -namespace NDetail { - -template <class TArgFormatter> -void FormatImpl( +template <CFormatter TFormatter> +void RunFormatter( TStringBuilderBase* builder, - TStringBuf format, - const TArgFormatter& argFormatter) + TStringBuf fmt, + const TFormatter& formatter) { size_t argIndex = 0; - auto current = format.begin(); + auto current = std::begin(fmt); + auto end = std::end(fmt); while (true) { // Scan verbatim part until stop symbol. auto verbatimBegin = current; - auto verbatimEnd = verbatimBegin; - while (verbatimEnd != format.end() && *verbatimEnd != '%') { - ++verbatimEnd; - } + auto verbatimEnd = std::find(current, end, IntroductorySymbol); // Copy verbatim part, if any. size_t verbatimSize = verbatimEnd - verbatimBegin; @@ -571,85 +731,86 @@ void FormatImpl( // Handle stop symbol. current = verbatimEnd; - if (current == format.end()) { + if (current == end) { break; } - YT_ASSERT(*current == '%'); + YT_ASSERT(*current == IntroductorySymbol); ++current; - if (*current == '%') { + if (*current == IntroductorySymbol) { // Verbatim %. - builder->AppendChar('%'); + builder->AppendChar(IntroductorySymbol); ++current; - } else { - // Scan format part until stop symbol. - auto argFormatBegin = current; - auto argFormatEnd = argFormatBegin; - bool singleQuotes = false; - bool doubleQuotes = false; + continue; + } - while ( - argFormatEnd != format.end() && - *argFormatEnd != GenericSpecSymbol && // value in generic format - *argFormatEnd != 'd' && // others are standard specifiers supported by printf - *argFormatEnd != 'i' && - *argFormatEnd != 'u' && - *argFormatEnd != 'o' && - *argFormatEnd != 'x' && - *argFormatEnd != 'X' && - *argFormatEnd != 'f' && - *argFormatEnd != 'F' && - *argFormatEnd != 'e' && - *argFormatEnd != 'E' && - *argFormatEnd != 'g' && - *argFormatEnd != 'G' && - *argFormatEnd != 'a' && - *argFormatEnd != 'A' && - *argFormatEnd != 'c' && - *argFormatEnd != 's' && - *argFormatEnd != 'p' && - *argFormatEnd != 'n') - { - switch (*argFormatEnd) { - case 'q': - singleQuotes = true; - break; - case 'Q': - doubleQuotes = true; - break; - case 'h': - break; - } - ++argFormatEnd; - } + // Scan format part until stop symbol. + auto argFormatBegin = current; + auto argFormatEnd = argFormatBegin; + bool singleQuotes = false; + bool doubleQuotes = false; - // Handle end of format string. - if (argFormatEnd != format.end()) { - ++argFormatEnd; + while ( + argFormatEnd != end && + *argFormatEnd != GenericSpecSymbol && // value in generic format + *argFormatEnd != 'd' && // others are standard specifiers supported by printf + *argFormatEnd != 'i' && + *argFormatEnd != 'u' && + *argFormatEnd != 'o' && + *argFormatEnd != 'x' && + *argFormatEnd != 'X' && + *argFormatEnd != 'f' && + *argFormatEnd != 'F' && + *argFormatEnd != 'e' && + *argFormatEnd != 'E' && + *argFormatEnd != 'g' && + *argFormatEnd != 'G' && + *argFormatEnd != 'a' && + *argFormatEnd != 'A' && + *argFormatEnd != 'c' && + *argFormatEnd != 's' && + *argFormatEnd != 'p' && + *argFormatEnd != 'n') + { + switch (*argFormatEnd) { + case 'q': + singleQuotes = true; + break; + case 'Q': + doubleQuotes = true; + break; + case 'h': + break; } + ++argFormatEnd; + } - // 'n' means 'nothing'; skip the argument. - if (*argFormatBegin != 'n') { - // Format argument. - TStringBuf argFormat(argFormatBegin, argFormatEnd); - if (singleQuotes) { - builder->AppendChar('\''); - } - if (doubleQuotes) { - builder->AppendChar('"'); - } - argFormatter(argIndex++, builder, argFormat); - if (singleQuotes) { - builder->AppendChar('\''); - } - if (doubleQuotes) { - builder->AppendChar('"'); - } - } + // Handle end of format string. + if (argFormatEnd != end) { + ++argFormatEnd; + } - current = argFormatEnd; + // 'n' means 'nothing'; skip the argument. + if (*argFormatBegin != 'n') { + // Format argument. + TStringBuf argFormat(argFormatBegin, argFormatEnd); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } + formatter(argIndex++, builder, argFormat); + if (singleQuotes) { + builder->AppendChar('\''); + } + if (doubleQuotes) { + builder->AppendChar('"'); + } } + + current = argFormatEnd; } } @@ -658,186 +819,86 @@ void FormatImpl( //////////////////////////////////////////////////////////////////////////////// template <class... TArgs> -TLazyMultiValueFormatter<TArgs...>::TLazyMultiValueFormatter( - TStringBuf format, - TArgs&&... args) - : Format_(format) - , Args_(std::forward<TArgs>(args)...) -{ } - -template <class... TArgs> -void FormatValue( - TStringBuilderBase* builder, - const TLazyMultiValueFormatter<TArgs...>& value, - TStringBuf /*format*/) +void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args) { - std::apply( - [&] <class... TInnerArgs> (TInnerArgs&&... args) { - builder->AppendFormat(value.Format_, std::forward<TInnerArgs>(args)...); - }, - value.Args_); + NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...); + NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter); } template <class... TArgs> -auto MakeLazyMultiValueFormatter(TStringBuf format, TArgs&&... args) -{ - return TLazyMultiValueFormatter<TArgs...>(format, std::forward<TArgs>(args)...); -} - -//////////////////////////////////////////////////////////////////////////////// - -template <class T> -struct TFormatTraits -{ - static constexpr bool HasCustomFormatValue = !std::is_same_v< - decltype(FormatValue( - static_cast<TStringBuilderBase*>(nullptr), - *static_cast<const T*>(nullptr), - TStringBuf())), - TToStringFallbackValueFormatterTag>; -}; - -//////////////////////////////////////////////////////////////////////////////// - -template <size_t IndexBase, class... TArgs> -struct TArgFormatterImpl; - -template <size_t IndexBase> -struct TArgFormatterImpl<IndexBase> -{ - void operator() (size_t /*index*/, TStringBuilderBase* builder, TStringBuf /*format*/) const - { - builder->AppendString(TStringBuf("<missing argument>")); - } -}; - -template <size_t IndexBase, class THeadArg, class... TTailArgs> -struct TArgFormatterImpl<IndexBase, THeadArg, TTailArgs...> -{ - explicit TArgFormatterImpl(const THeadArg& headArg, const TTailArgs&... tailArgs) - : HeadArg(headArg) - , TailFormatter(tailArgs...) - { } - - const THeadArg& HeadArg; - TArgFormatterImpl<IndexBase + 1, TTailArgs...> TailFormatter; - - void operator() (size_t index, TStringBuilderBase* builder, TStringBuf format) const - { - YT_ASSERT(index >= IndexBase); - if (index == IndexBase) { - FormatValue(builder, HeadArg, format); - } else { - TailFormatter(index, builder, format); - } - } -}; - -template <typename TVectorElement> -struct TSpanArgFormatterImpl +void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args) { - explicit TSpanArgFormatterImpl(std::span<TVectorElement> v) - : Span_(v) - { } - - explicit TSpanArgFormatterImpl(const std::vector<TVectorElement>& v) - : Span_(v.begin(), v.size()) - { } - - explicit TSpanArgFormatterImpl(const TVector<TVectorElement>& v) - : Span_(v.begin(), v.size()) - { } - - std::span<const TVectorElement> Span_; - - void operator() (size_t index, TStringBuilderBase* builder, TStringBuf format) const - { - if (index >= Span_.size()) { - builder->AppendString(TStringBuf("<missing argument>")); - } else { - FormatValue(builder, *(Span_.begin() + index), 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 = [] { + TStaticFormat<TArgs...>::CheckFormattability(); + return 42; + } (); + Y_UNUSED(argsChecker); -template <size_t Length, class... TArgs> -void Format( - TStringBuilderBase* builder, - const char (&format)[Length], - TArgs&&... args) -{ - Format(builder, TStringBuf(format, Length - 1), std::forward<TArgs>(args)...); + NYT::NDetail::TValueFormatter<0, TArgs...> formatter(args...); + NYT::NDetail::RunFormatter(builder, fmt.Get(), formatter); } template <class... TArgs> -void Format( - TStringBuilderBase* builder, - TStringBuf format, - TArgs&&... args) -{ - TArgFormatterImpl<0, TArgs...> argFormatter(args...); - NYT::NDetail::FormatImpl(builder, format, argFormatter); -} - -template <size_t Length, class... TArgs> -TString Format( - const char (&format)[Length], - TArgs&&... args) +TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args) { TStringBuilder builder; - Format(&builder, format, std::forward<TArgs>(args)...); + Format(&builder, fmt, std::forward<TArgs>(args)...); return builder.Flush(); } template <class... TArgs> -TString Format( - TStringBuf format, - TArgs&&... args) +TString Format(TRuntimeFormat fmt, TArgs&&... args) { TStringBuilder builder; - Format(&builder, format, std::forward<TArgs>(args)...); + Format(&builder, fmt, std::forward<TArgs>(args)...); return builder.Flush(); } +//////////////////////////////////////////////////////////////////////////////// + template <size_t Length, class TVector> void FormatVector( TStringBuilderBase* builder, - const char (&format)[Length], + const char (&fmt)[Length], const TVector& vec) { - TSpanArgFormatterImpl formatter(vec); - NYT::NDetail::FormatImpl(builder, format, formatter); + NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec); + NYT::NDetail::RunFormatter(builder, fmt, formatter); } template <class TVector> void FormatVector( TStringBuilderBase* builder, - TStringBuf format, + TStringBuf fmt, const TVector& vec) { - TSpanArgFormatterImpl formatter(vec); - NYT::NDetail::FormatImpl(builder, format, formatter); + NYT::NDetail::TRangeFormatter<typename TVector::value_type> formatter(vec); + NYT::NDetail::RunFormatter(builder, fmt, formatter); } template <size_t Length, class TVector> TString FormatVector( - const char (&format)[Length], + const char (&fmt)[Length], const TVector& vec) { TStringBuilder builder; - FormatVector(&builder, format, vec); + FormatVector(&builder, fmt, vec); return builder.Flush(); } template <class TVector> TString FormatVector( - TStringBuf format, + TStringBuf fmt, const TVector& vec) { TStringBuilder builder; - FormatVector(&builder, format, vec); + FormatVector(&builder, fmt, vec); return builder.Flush(); } diff --git a/library/cpp/yt/string/format.h b/library/cpp/yt/string/format.h index 8e0d5f82e50..6463135f89c 100644 --- a/library/cpp/yt/string/format.h +++ b/library/cpp/yt/string/format.h @@ -54,15 +54,15 @@ namespace NYT { * */ -template <size_t Length, class... TArgs> -void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args); template <class... TArgs> -void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args); +void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args); +template <class... TArgs> +void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args); -template <size_t Length, class... TArgs> -TString Format(const char (&format)[Length], TArgs&&... args); template <class... TArgs> -TString Format(TStringBuf format, TArgs&&... args); +TString Format(TStaticFormat<TArgs...> fmt, TArgs&&... args); +template <class... TArgs> +TString Format(TRuntimeFormat fmt, TArgs&&... args); //////////////////////////////////////////////////////////////////////////////// @@ -101,6 +101,19 @@ struct TFormatterWrapper TFormatter Formatter; }; +// Allows insertion of text conditionally. +// Usage: +/* + NYT::Format( + "Value is %v%v", + 42, + MakeFormatterWrapper([&] (auto* builder) { + If (PossiblyMissingInfo_) { + builder->AppendString(", PossiblyMissingInfo: "); + FormatValue(builder, PossiblyMissingInfo_, "v"); + } + })); + */ template <class TFormatter> TFormatterWrapper<TFormatter> MakeFormatterWrapper( TFormatter&& formatter); @@ -114,7 +127,7 @@ template <class... TArgs> void FormatValue( TStringBuilderBase* builder, const TLazyMultiValueFormatter<TArgs...>& value, - TStringBuf /*format*/); + TStringBuf /*spec*/); //! A wrapper for a bunch of values that formats them lazily on demand. /*! @@ -129,12 +142,19 @@ class TLazyMultiValueFormatter : private TNonCopyable { public: - TLazyMultiValueFormatter(TStringBuf format, TArgs&&... args); + TLazyMultiValueFormatter(TStringBuf fmt, TArgs&&... args); + // NB(arkady-e1ppa): We actually have to + // forward declare this method as above + // and friend-declare it as specialization + // here because clang is stupid and would + // treat this friend declartion as a hidden friend + // declaration which in turn is treated as a separate symbol + // causing linker to not find the actual definition. friend void FormatValue<>( TStringBuilderBase* builder, const TLazyMultiValueFormatter& value, - TStringBuf /*format*/); + TStringBuf /*spec*/); private: const TStringBuf Format_; @@ -142,7 +162,37 @@ private: }; template <class ... Args> -auto MakeLazyMultiValueFormatter(TStringBuf format, Args&&... args); +auto MakeLazyMultiValueFormatter(TStringBuf fmt, Args&&... args); + +//////////////////////////////////////////////////////////////////////////////// + +/* + Example: + + FormatVector("One: %v, Two: %v, Three: %v", {1, 2, 3}) + => "One: 1, Two: 2, Three: 3" +*/ +template <size_t Length, class TVector> +void FormatVector( + TStringBuilderBase* builder, + const char (&fmt)[Length], + const TVector& vec); + +template <class TVector> +void FormatVector( + TStringBuilderBase* builder, + TStringBuf fmt, + const TVector& vec); + +template <size_t Length, class TVector> +TString FormatVector( + const char (&fmt)[Length], + const TVector& vec); + +template <class TVector> +TString FormatVector( + TStringBuf fmt, + const TVector& vec); //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/format_analyser-inl.h b/library/cpp/yt/string/format_analyser-inl.h index a548a06072a..ecc83ebccb4 100644 --- a/library/cpp/yt/string/format_analyser-inl.h +++ b/library/cpp/yt/string/format_analyser-inl.h @@ -4,11 +4,11 @@ #include "format_analyser.h" #endif -namespace NYT { +#include "format_arg.h" -//////////////////////////////////////////////////////////////////////////////// +namespace NYT::NDetail { -namespace NDetail { +//////////////////////////////////////////////////////////////////////////////// consteval bool Contains(std::string_view sv, char symbol) { @@ -93,7 +93,9 @@ consteval void TFormatAnalyser::ValidateFormat(std::string_view fmt) template <class TArg> consteval auto TFormatAnalyser::GetSpecifiers() { - static_assert(CFormattable<TArg>, "Your specialization of TFormatArg is broken"); + if constexpr (!CFormattable<TArg>) { + CrashCompilerNotFormattable<TArg>("Your specialization of TFormatArg is broken"); + } return TSpecifiers{ .Conversion = std::string_view{ @@ -105,39 +107,6 @@ consteval auto TFormatAnalyser::GetSpecifiers() }; } -} // namespace NDetail - //////////////////////////////////////////////////////////////////////////////// -template <bool Hot, size_t N, std::array<char, N> List, class TFrom> -consteval auto TFormatArgBase::ExtendConversion() -{ - static_assert(CFormattable<TFrom>); - return AppendArrays<Hot, N, List, &TFrom::ConversionSpecifiers>(); -} - -template <bool Hot, size_t N, std::array<char, N> List, class TFrom> -consteval auto TFormatArgBase::ExtendFlags() -{ - static_assert(CFormattable<TFrom>); - return AppendArrays<Hot, N, List, &TFrom::FlagSpecifiers>(); -} - -template <bool Hot, size_t N, std::array<char, N> List, auto* From> -consteval auto TFormatArgBase::AppendArrays() -{ - auto& from = *From; - return [] <size_t... I, size_t... J> ( - std::index_sequence<I...>, - std::index_sequence<J...>) { - if constexpr (Hot) { - return std::array{List[J]..., from[I]...}; - } else { - return std::array{from[I]..., List[J]...}; - } - } ( - std::make_index_sequence<std::size(from)>(), - std::make_index_sequence<N>()); - } - } // namespace NYT::NDetail diff --git a/library/cpp/yt/string/format_analyser.h b/library/cpp/yt/string/format_analyser.h index 4afe3f1ca8b..9a05e3eb8cf 100644 --- a/library/cpp/yt/string/format_analyser.h +++ b/library/cpp/yt/string/format_analyser.h @@ -1,14 +1,14 @@ #pragma once +#include <util/generic/strbuf.h> + #include <array> #include <string_view> -namespace NYT { +namespace NYT::NDetail { //////////////////////////////////////////////////////////////////////////////// -namespace NDetail { - struct TFormatAnalyser { public: @@ -18,6 +18,9 @@ public: private: // Non-constexpr function call will terminate compilation. // Purposefully undefined and non-constexpr/consteval + template <class T> + static void CrashCompilerNotFormattable(std::string_view /*msg*/) + { /*Suppress "internal linkage but undefined" warning*/ } static void CrashCompilerNotEnoughArguments(std::string_view msg); static void CrashCompilerTooManyArguments(std::string_view msg); static void CrashCompilerWrongTermination(std::string_view msg); @@ -35,67 +38,9 @@ private: static constexpr char IntroductorySymbol = '%'; }; -} // namespace NDetail - //////////////////////////////////////////////////////////////////////////////// -// Base used for flag checks for each type independently. -// Use it for overrides. -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' - }; - - static constexpr std::array FlagSpecifiers = { - '-', '+', ' ', '#', '0', - '1', '2', '3', '4', '5', - '6', '7', '8', '9', - '*', '.', 'h', 'l', 'j', - 'z', 't', 'L', 'q', 'Q' - }; - - template <class T> - static constexpr bool IsSpecifierList = requires (T t) { - [] <size_t N> (std::array<char, N>) { } (t); - }; - - // Hot = |true| adds specifiers to the beggining - // of a new array. - template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> - static consteval auto ExtendConversion(); - - template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> - static consteval auto ExtendFlags(); - -private: - template <bool Hot, size_t N, std::array<char, N> List, auto* From> - static consteval auto AppendArrays(); -}; - -//////////////////////////////////////////////////////////////////////////////// - -template <class T> -struct TFormatArg - : public TFormatArgBase -{ }; - -// Semantic requirement: -// Said field must be constexpr. -template <class T> -concept CFormattable = requires { - TFormatArg<T>::ConversionSpecifiers; - requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>; - - TFormatArg<T>::FlagSpecifiers; - requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>; -}; - -} // namespace NYT +} // namespace NYT::NDetail #define FORMAT_ANALYSER_INL_H_ #include "format_analyser-inl.h" diff --git a/library/cpp/yt/string/format_arg-inl.h b/library/cpp/yt/string/format_arg-inl.h new file mode 100644 index 00000000000..82a77a1249a --- /dev/null +++ b/library/cpp/yt/string/format_arg-inl.h @@ -0,0 +1,67 @@ +#ifndef FORMAT_ARG_INL_H_ +#error "Direct inclusion of this file is not allowed, include format_arg.h" +// For the sake of sane code completion. +#include "format_arg.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T> +constexpr std::string_view QualidName() +{ + constexpr size_t openBracketOffset = 5; + constexpr size_t closeBracketOffset = 1; + constexpr std::string_view func = __PRETTY_FUNCTION__; + constexpr auto left = std::find(std::begin(func), std::end(func), '[') + openBracketOffset; + return std::string_view(left, std::prev(std::end(func), closeBracketOffset)); +} + +template <class T> +constexpr bool IsNYTName() +{ + constexpr auto qualidName = QualidName<T>(); + return qualidName.find("NYT::") == 0; +} + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +template <bool Hot, size_t N, std::array<char, N> List, class TFrom> +consteval auto TFormatArgBase::ExtendConversion() +{ + static_assert(std::same_as<TFrom, TFormatArgBase> || CFormattable<TFrom>); + return AppendArrays<Hot, N, List, &TFormatArg<TFrom>::ConversionSpecifiers>(); +} + +template <bool Hot, size_t N, std::array<char, N> List, class TFrom> +consteval auto TFormatArgBase::ExtendFlags() +{ + static_assert(std::same_as<TFrom, TFormatArgBase> || CFormattable<TFrom>); + return AppendArrays<Hot, N, List, &TFormatArg<TFrom>::FlagSpecifiers>(); +} + +template <bool Hot, size_t N, std::array<char, N> List, auto* From> +consteval auto TFormatArgBase::AppendArrays() +{ + auto& from = *From; + return [] <size_t... I, size_t... J> ( + std::index_sequence<I...>, + std::index_sequence<J...>) { + if constexpr (Hot) { + return std::array{List[J]..., from[I]...}; + } else { + return std::array{from[I]..., List[J]...}; + } + } ( + std::make_index_sequence<std::size(from)>(), + std::make_index_sequence<N>()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/string/format_arg.h b/library/cpp/yt/string/format_arg.h new file mode 100644 index 00000000000..8c4f354f98e --- /dev/null +++ b/library/cpp/yt/string/format_arg.h @@ -0,0 +1,103 @@ +#pragma once + +#include <util/generic/strbuf.h> + +#include <array> +#include <string_view> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +class TStringBuilderBase; + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T> +constexpr std::string_view QualidName(); + +template <class T> +constexpr bool IsNYTName(); + +template <class T> +concept CYtName = IsNYTName<T>(); + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +// Base used for flag checks for each type independently. +// Use it for overrides. +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' + }; + + static constexpr std::array FlagSpecifiers = { + '-', '+', ' ', '#', '0', + '1', '2', '3', '4', '5', + '6', '7', '8', '9', + '*', '.', 'h', 'l', 'j', + 'z', 't', 'L', 'q', 'Q' + }; + + template <class T> + static constexpr bool IsSpecifierList = requires (T t) { + [] <size_t N> (std::array<char, N>) { } (t); + }; + + // Hot = |true| adds specifiers to the beggining + // of a new array. + template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> + static consteval auto ExtendConversion(); + + template <bool Hot, size_t N, std::array<char, N> List, class TFrom = TFormatArgBase> + static consteval auto ExtendFlags(); + +private: + template <bool Hot, size_t N, std::array<char, N> List, auto* From> + static consteval auto AppendArrays(); +}; + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +struct TFormatArg + : public TFormatArgBase +{ }; + +// Ultimate customization point mechanism --- define an overload +// of FormatValue in order to support formatting of your type. +// Semantic requirement: +// Said field must be constexpr. +template <class T> +concept CFormattable = + requires { + TFormatArg<T>::ConversionSpecifiers; + requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::ConversionSpecifiers)>; + + TFormatArg<T>::FlagSpecifiers; + requires TFormatArgBase::IsSpecifierList<decltype(TFormatArg<T>::FlagSpecifiers)>; + } && + requires ( + TStringBuilderBase* builder, + const T& value, + TStringBuf spec + ) { + { FormatValue(builder, value, spec) } -> std::same_as<void>; + }; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define FORMAT_ARG_INL_H_ +#include "format_arg-inl.h" +#undef FORMAT_ARG_INL_H_ diff --git a/library/cpp/yt/string/format_string-inl.h b/library/cpp/yt/string/format_string-inl.h new file mode 100644 index 00000000000..5187a2ac6b0 --- /dev/null +++ b/library/cpp/yt/string/format_string-inl.h @@ -0,0 +1,56 @@ +#ifndef FORMAT_STRING_INL_H_ +#error "Direct inclusion of this file is not allowed, include format_string.h" +// For the sake of sane code completion. +#include "format_string.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class... TArgs> +template <class T> + requires std::constructible_from<std::string_view, T> +consteval TBasicStaticFormat<TArgs...>::TBasicStaticFormat(const T& fmt) + : Format_(fmt) +{ + CheckFormattability(); +#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS) + NDetail::TFormatAnalyser::ValidateFormat<std::remove_cvref_t<TArgs>...>(Format_); +#endif +} + +template <class... TArgs> +TStringBuf TBasicStaticFormat<TArgs...>::Get() const noexcept +{ + return {Format_}; +} + +template <class... TArgs> +consteval void TBasicStaticFormat<TArgs...>::CheckFormattability() +{ +#if !defined(NDEBUG) && !defined(YT_DISABLE_FORMAT_STATIC_ANALYSIS) + using TTuple = std::tuple<std::remove_cvref_t<TArgs>...>; + + [] <size_t... Idx> (std::index_sequence<Idx...>) { + ([] { + if constexpr (!CFormattable<std::tuple_element_t<Idx, TTuple>>) { + CrashCompilerClassIsNotFormattable<std::tuple_element_t<Idx, TTuple>>(); + } + } (), ...); + } (std::index_sequence_for<TArgs...>()); +#endif +} + +inline TRuntimeFormat::TRuntimeFormat(TStringBuf fmt) + : Format_(fmt) +{ } + +inline 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 new file mode 100644 index 00000000000..8eeab7d99df --- /dev/null +++ b/library/cpp/yt/string/format_string.h @@ -0,0 +1,61 @@ +#pragma once + +#include "format_analyser.h" + +#include <util/generic/strbuf.h> + +#if (!__clang__ || __clang_major__ < 16) + #define YT_DISABLE_FORMAT_STATIC_ANALYSIS +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// 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 +{ +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); + + TStringBuf Get() const noexcept; + + static consteval void CheckFormattability(); + +private: + std::string_view Format_; + + template <class T> + 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>...>; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define FORMAT_STRING_INL_H_ +#include "format_string-inl.h" +#undef FORMAT_STRING_INL_H_ diff --git a/library/cpp/yt/string/guid.cpp b/library/cpp/yt/string/guid.cpp index 6c133a97789..2a781d032d6 100644 --- a/library/cpp/yt/string/guid.cpp +++ b/library/cpp/yt/string/guid.cpp @@ -11,11 +11,6 @@ void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/ builder->Advance(end - begin); } -TString ToString(TGuid guid) -{ - return ToStringViaBuilder(guid); -} - //////////////////////////////////////////////////////////////////////////////// } // namespace NYT diff --git a/library/cpp/yt/string/guid.h b/library/cpp/yt/string/guid.h index 75edbce5dbb..de169934861 100644 --- a/library/cpp/yt/string/guid.h +++ b/library/cpp/yt/string/guid.h @@ -7,7 +7,6 @@ namespace NYT { //////////////////////////////////////////////////////////////////////////////// void FormatValue(TStringBuilderBase* builder, TGuid value, TStringBuf /*format*/); -TString ToString(TGuid guid); //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/string_builder-inl.h b/library/cpp/yt/string/string_builder-inl.h index 414667954f3..683a241c9cc 100644 --- a/library/cpp/yt/string/string_builder-inl.h +++ b/library/cpp/yt/string/string_builder-inl.h @@ -6,6 +6,8 @@ #include <library/cpp/yt/assert/assert.h> +#include <util/stream/str.h> + namespace NYT { //////////////////////////////////////////////////////////////////////////////// @@ -81,13 +83,13 @@ inline void TStringBuilderBase::Reset() template <class... TArgs> void TStringBuilderBase::AppendFormat(TStringBuf format, TArgs&& ... args) { - Format(this, format, std::forward<TArgs>(args)...); + Format(this, TRuntimeFormat{format}, std::forward<TArgs>(args)...); } template <size_t Length, class... TArgs> void TStringBuilderBase::AppendFormat(const char (&format)[Length], TArgs&& ... args) { - Format(this, format, std::forward<TArgs>(args)...); + Format(this, TRuntimeFormat{format}, std::forward<TArgs>(args)...); } //////////////////////////////////////////////////////////////////////////////// @@ -131,4 +133,52 @@ TString ToStringViaBuilder(const T& value, TStringBuf spec) //////////////////////////////////////////////////////////////////////////////// +// Compatibility for users of NYT::ToString(nyt_type). +template <CFormattable T> +TString ToString(const T& t) +{ + return ToStringViaBuilder(t); +} + +// Sometime we want to implement +// FormatValue using util's ToString +// However, if we inside the FormatValue +// we cannot just call the ToString since +// in this scope T is already CFormattable +// and ToString will call the very +// FormatValue we are implementing, +// causing an infinite recursion loop. +// This method is basically a call to +// util's ToString default implementation. +template <class T> +TString ToStringIgnoringFormatValue(const T& t) +{ + TString s; + ::TStringOutput o(s); + o << t; + return s; +} + +//////////////////////////////////////////////////////////////////////////////// + } // namespace NYT + +#include <util/string/cast.h> + +// util/string/cast.h extension for yt 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>) +struct TToString<T, false> +{ + static TString Cvt(const T& t) + { + return NYT::ToStringViaBuilder(t); + } +}; + +} // namespace NPrivate diff --git a/library/cpp/yt/string/string_builder.h b/library/cpp/yt/string/string_builder.h index 4ac90201770..122e5d19a0a 100644 --- a/library/cpp/yt/string/string_builder.h +++ b/library/cpp/yt/string/string_builder.h @@ -1,5 +1,7 @@ #pragma once +#include "format_string.h" + #include <util/generic/string.h> namespace NYT { @@ -11,10 +13,10 @@ class TStringBuilderBase; class TStringBuilder; class TDelimitedStringBuilderWrapper; -template <size_t Length, class... TArgs> -void Format(TStringBuilderBase* builder, const char (&format)[Length], TArgs&&... args); template <class... TArgs> -void Format(TStringBuilderBase* builder, TStringBuf format, TArgs&&... args); +void Format(TStringBuilderBase* builder, TStaticFormat<TArgs...> fmt, TArgs&&... args); +template <class... TArgs> +void Format(TStringBuilderBase* builder, TRuntimeFormat fmt, TArgs&&... args); //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/unittests/enum_ut.cpp b/library/cpp/yt/string/unittests/enum_ut.cpp index 2c368e85d45..be5387151a0 100644 --- a/library/cpp/yt/string/unittests/enum_ut.cpp +++ b/library/cpp/yt/string/unittests/enum_ut.cpp @@ -12,8 +12,8 @@ namespace { // Some compile-time sanity checks. DEFINE_ENUM(ESample, (One)(Two)); -static_assert(TFormatTraits<ESample>::HasCustomFormatValue); -static_assert(TFormatTraits<TEnumIndexedArray<ESample, int>>::HasCustomFormatValue); +static_assert(CFormattable<ESample>); +static_assert(CFormattable<TEnumIndexedArray<ESample, int>>); DEFINE_ENUM(EColor, (Red) diff --git a/library/cpp/yt/string/unittests/format_ut.cpp b/library/cpp/yt/string/unittests/format_ut.cpp index 80162747ccb..14b783516c9 100644 --- a/library/cpp/yt/string/unittests/format_ut.cpp +++ b/library/cpp/yt/string/unittests/format_ut.cpp @@ -4,6 +4,8 @@ #include <library/cpp/yt/small_containers/compact_vector.h> +#include <util/generic/hash_set.h> + #include <limits> namespace NYT { @@ -12,31 +14,31 @@ namespace { //////////////////////////////////////////////////////////////////////////////// // Some compile-time sanity checks. -static_assert(TFormatTraits<int>::HasCustomFormatValue); -static_assert(TFormatTraits<double>::HasCustomFormatValue); -static_assert(TFormatTraits<void*>::HasCustomFormatValue); -static_assert(TFormatTraits<const char*>::HasCustomFormatValue); -static_assert(TFormatTraits<TStringBuf>::HasCustomFormatValue); -static_assert(TFormatTraits<TString>::HasCustomFormatValue); -static_assert(TFormatTraits<std::span<int>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::vector<int>>::HasCustomFormatValue); +static_assert(CFormattable<int>); +static_assert(CFormattable<double>); +static_assert(CFormattable<void*>); +static_assert(CFormattable<const char*>); +static_assert(CFormattable<TStringBuf>); +static_assert(CFormattable<TString>); +static_assert(CFormattable<std::span<int>>); +static_assert(CFormattable<std::vector<int>>); // N.B. TCompactVector<int, 1> is not buildable on Windows -static_assert(TFormatTraits<TCompactVector<int, 2>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::set<int>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::map<int, int>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::multimap<int, int>>::HasCustomFormatValue); -static_assert(TFormatTraits<THashSet<int>>::HasCustomFormatValue); -static_assert(TFormatTraits<THashMap<int, int>>::HasCustomFormatValue); -static_assert(TFormatTraits<THashMultiSet<int>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::pair<int, int>>::HasCustomFormatValue); -static_assert(TFormatTraits<std::optional<int>>::HasCustomFormatValue); -static_assert(TFormatTraits<TDuration>::HasCustomFormatValue); -static_assert(TFormatTraits<TInstant>::HasCustomFormatValue); +static_assert(CFormattable<TCompactVector<int, 2>>); +static_assert(CFormattable<std::set<int>>); +static_assert(CFormattable<std::map<int, int>>); +static_assert(CFormattable<std::multimap<int, int>>); +static_assert(CFormattable<THashSet<int>>); +static_assert(CFormattable<THashMap<int, int>>); +static_assert(CFormattable<THashMultiSet<int>>); +static_assert(CFormattable<std::pair<int, int>>); +static_assert(CFormattable<std::optional<int>>); +static_assert(CFormattable<TDuration>); +static_assert(CFormattable<TInstant>); struct TUnformattable { }; -static_assert(!TFormatTraits<TUnformattable>::HasCustomFormatValue); +static_assert(!CFormattable<TUnformattable>); //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/string/unittests/guid_ut.cpp b/library/cpp/yt/string/unittests/guid_ut.cpp index 4b5eebea16e..ed02b873917 100644 --- a/library/cpp/yt/string/unittests/guid_ut.cpp +++ b/library/cpp/yt/string/unittests/guid_ut.cpp @@ -10,7 +10,7 @@ namespace { //////////////////////////////////////////////////////////////////////////////// -static_assert(TFormatTraits<TGuid>::HasCustomFormatValue); +static_assert(CFormattable<TGuid>); TString CanonicalToString(TGuid value) { diff --git a/library/cpp/yt/yson_string/convert.cpp b/library/cpp/yt/yson_string/convert.cpp index 51a646f0ac6..6d78ea6c9db 100644 --- a/library/cpp/yt/yson_string/convert.cpp +++ b/library/cpp/yt/yson_string/convert.cpp @@ -234,7 +234,8 @@ TString ParseStringFromYsonString(const TYsonStringBuf& str) throw TYsonLiteralParseException(ex, "Failed to decode string length"); } if (length < 0) { - throw TYsonLiteralParseException(Format("Negative string length ", + throw TYsonLiteralParseException(Format( + "Negative string length %v", length)); } if (static_cast<i64>(input.Avail()) != length) { diff --git a/library/cpp/yt/yson_string/string.cpp b/library/cpp/yt/yson_string/string.cpp index 164e450b72c..ed2990a51b0 100644 --- a/library/cpp/yt/yson_string/string.cpp +++ b/library/cpp/yt/yson_string/string.cpp @@ -197,14 +197,14 @@ void TYsonString::Load(IInputStream* s) //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TYsonString& yson) +void FormatValue(TStringBuilderBase* builder, const TYsonString& yson, TStringBuf spec) { - return yson.ToString(); + FormatValue(builder, yson.ToString(), spec); } -TString ToString(const TYsonStringBuf& yson) +void FormatValue(TStringBuilderBase* builder, const TYsonStringBuf& yson, TStringBuf spec) { - return TString(yson.AsStringBuf()); + FormatValue(builder, yson.AsStringBuf(), spec); } //////////////////////////////////////////////////////////////////////////////// diff --git a/library/cpp/yt/yson_string/string.h b/library/cpp/yt/yson_string/string.h index d9f036569f0..e310405ebcb 100644 --- a/library/cpp/yt/yson_string/string.h +++ b/library/cpp/yt/yson_string/string.h @@ -4,6 +4,8 @@ #include <library/cpp/yt/memory/ref.h> +#include <library/cpp/yt/string/format.h> + #include <variant> namespace NYT::NYson { @@ -134,8 +136,8 @@ bool operator != (const TYsonString& lhs, const TYsonStringBuf& rhs); bool operator != (const TYsonStringBuf& lhs, const TYsonString& rhs); bool operator != (const TYsonStringBuf& lhs, const TYsonStringBuf& rhs); -TString ToString(const TYsonString& yson); -TString ToString(const TYsonStringBuf& yson); +void FormatValue(TStringBuilderBase* builder, const TYsonString& yson, TStringBuf spec); +void FormatValue(TStringBuilderBase* builder, const TYsonStringBuf& yson, TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/chaos_client/replication_card.cpp b/yt/yt/client/chaos_client/replication_card.cpp index fa75f29ccb7..dcafa48f632 100644 --- a/yt/yt/client/chaos_client/replication_card.cpp +++ b/yt/yt/client/chaos_client/replication_card.cpp @@ -111,11 +111,6 @@ void FormatValue(TStringBuilderBase* builder, const TReplicationCardFetchOptions options.IncludeHistory); } -TString ToString(const TReplicationCardFetchOptions& options) -{ - return ToStringViaBuilder(options); -} - bool TReplicationCardFetchOptions::Contains(const TReplicationCardFetchOptions& other) const { auto selfMask = NDetail::ToBitMask(*this); @@ -144,11 +139,6 @@ void FormatValue( } -TString ToString(const TReplicationProgress& replicationProgress) -{ - return ToStringViaBuilder(replicationProgress); -} - void FormatValue(TStringBuilderBase* builder, const TReplicaHistoryItem& replicaHistoryItem, TStringBuf /*spec*/) { builder->AppendFormat("{Era: %v, Timestamp: %v, Mode: %v, State: %v}", @@ -158,11 +148,6 @@ void FormatValue(TStringBuilderBase* builder, const TReplicaHistoryItem& replica replicaHistoryItem.State); } -TString ToString(const TReplicaHistoryItem& replicaHistoryItem) -{ - return ToStringViaBuilder(replicaHistoryItem); -} - void FormatValue( TStringBuilderBase* builder, const TReplicaInfo& replicaInfo, @@ -181,11 +166,6 @@ void FormatValue( builder->AppendFormat(", History: %v}", replicaInfo.History); } -TString ToString(const TReplicaInfo& replicaInfo) -{ - return ToStringViaBuilder(replicaInfo); -} - void FormatValue( TStringBuilderBase* builder, const TReplicationCard& replicationCard, diff --git a/yt/yt/client/chaos_client/replication_card.h b/yt/yt/client/chaos_client/replication_card.h index 62daba4e145..9581f887f76 100644 --- a/yt/yt/client/chaos_client/replication_card.h +++ b/yt/yt/client/chaos_client/replication_card.h @@ -96,7 +96,6 @@ struct TReplicationCardFetchOptions }; void FormatValue(TStringBuilderBase* builder, const TReplicationCardFetchOptions& options, TStringBuf /*spec*/); -TString ToString(const TReplicationCardFetchOptions& options); /////////////////////////////////////////////////////////////////////////////// @@ -105,17 +104,14 @@ void FormatValue( const TReplicationProgress& replicationProgress, TStringBuf /*spec*/, std::optional<TReplicationProgressProjection> replicationProgressProjection = std::nullopt); -TString ToString(const TReplicationProgress& replicationProgress); void FormatValue(TStringBuilderBase* builder, const TReplicaHistoryItem& replicaHistoryItem, TStringBuf /*spec*/); -TString ToString(const TReplicaHistoryItem& replicaHistoryItem); void FormatValue( TStringBuilderBase* builder, const TReplicaInfo& replicaInfo, TStringBuf /*spec*/, std::optional<TReplicationProgressProjection> replicationProgressProjection = std::nullopt); -TString ToString(const TReplicaInfo& replicaInfo); void FormatValue( TStringBuilderBase* builder, diff --git a/yt/yt/client/chunk_client/chunk_replica.cpp b/yt/yt/client/chunk_client/chunk_replica.cpp index a2262c04f3f..4fe3c8335d4 100644 --- a/yt/yt/client/chunk_client/chunk_replica.cpp +++ b/yt/yt/client/chunk_client/chunk_replica.cpp @@ -82,11 +82,6 @@ void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithLocation replica, builder->AppendFormat("@%v", replica.GetChunkLocationUuid()); } -TString ToString(TChunkReplicaWithLocation replica) -{ - return ToStringViaBuilder(replica); -} - void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithMedium replica, TStringBuf /*spec*/) { builder->AppendFormat("%v", replica.GetNodeId()); @@ -100,11 +95,6 @@ void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithMedium replica, T } } -TString ToString(TChunkReplicaWithMedium replica) -{ - return ToStringViaBuilder(replica); -} - void FormatValue(TStringBuilderBase* builder, TChunkReplica replica, TStringBuf /*spec*/) { builder->AppendFormat("%v", replica.GetNodeId()); @@ -113,11 +103,6 @@ void FormatValue(TStringBuilderBase* builder, TChunkReplica replica, TStringBuf } } -TString ToString(TChunkReplica replica) -{ - return ToStringViaBuilder(replica); -} - void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndex& id, TStringBuf /*spec*/) { builder->AppendFormat("%v", id.Id); @@ -126,11 +111,6 @@ void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndex& id, TStri } } -TString ToString(const TChunkIdWithIndex& id) -{ - return ToStringViaBuilder(id); -} - void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndexes& id, TStringBuf /*spec*/) { builder->AppendFormat("%v", id.Id); @@ -144,11 +124,6 @@ void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndexes& id, TSt } } -TString ToString(const TChunkIdWithIndexes& id) -{ - return ToStringViaBuilder(id); -} - //////////////////////////////////////////////////////////////////////////////// TChunkReplicaAddressFormatter::TChunkReplicaAddressFormatter(TNodeDirectoryPtr nodeDirectory) diff --git a/yt/yt/client/chunk_client/chunk_replica.h b/yt/yt/client/chunk_client/chunk_replica.h index 11ff43d52c9..520c4e964ce 100644 --- a/yt/yt/client/chunk_client/chunk_replica.h +++ b/yt/yt/client/chunk_client/chunk_replica.h @@ -54,7 +54,6 @@ void ToProto(ui32* value, TChunkReplicaWithMedium replica) = delete; void FromProto(TChunkReplicaWithMedium* replica, ui32 value) = delete; void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithMedium replica, TStringBuf spec); -TString ToString(TChunkReplicaWithMedium replica); //////////////////////////////////////////////////////////////////////////////// @@ -86,7 +85,6 @@ private: }; void FormatValue(TStringBuilderBase* builder, TChunkReplicaWithLocation replica, TStringBuf spec); -TString ToString(TChunkReplicaWithLocation replica); //////////////////////////////////////////////////////////////////////////////// @@ -115,7 +113,6 @@ private: }; void FormatValue(TStringBuilderBase* builder, TChunkReplica replica, TStringBuf spec); -TString ToString(TChunkReplica replica); //////////////////////////////////////////////////////////////////////////////// @@ -154,13 +151,13 @@ struct TChunkIdWithIndexes bool operator<(const TChunkIdWithIndex& lhs, const TChunkIdWithIndex& rhs); -TString ToString(const TChunkIdWithIndex& id); +void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndex& id, TStringBuf spec = {}); //////////////////////////////////////////////////////////////////////////////// bool operator<(const TChunkIdWithIndexes& lhs, const TChunkIdWithIndexes& rhs); -TString ToString(const TChunkIdWithIndexes& id); +void FormatValue(TStringBuilderBase* builder, const TChunkIdWithIndexes& id, TStringBuf spec = {}); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/chunk_client/data_statistics.cpp b/yt/yt/client/chunk_client/data_statistics.cpp index 34327adbf7c..1124abd6729 100644 --- a/yt/yt/client/chunk_client/data_statistics.cpp +++ b/yt/yt/client/chunk_client/data_statistics.cpp @@ -211,18 +211,13 @@ TDuration TCodecStatistics::GetTotalDuration() const void FormatValue(TStringBuilderBase* builder, const TCodecStatistics& statistics, TStringBuf /* spec */) { - FormatKeyValueRange(builder, statistics.CodecToDuration(), TDefaultFormatter()); + ::NYT::FormatKeyValueRange(builder, statistics.CodecToDuration(), TDefaultFormatter()); if (statistics.ValueDictionaryCompressionDuration() != TDuration::Zero()) { builder->AppendFormat(", ValueDictionaryCompressionDuration: %v", statistics.ValueDictionaryCompressionDuration()); } } -TString ToString(const TCodecStatistics& statistics) -{ - return ToStringViaBuilder(statistics); -} - //////////////////////////////////////////////////////////////////////////////// } // namespace NYT::NChunkClient diff --git a/yt/yt/client/chunk_client/data_statistics.h b/yt/yt/client/chunk_client/data_statistics.h index 0a8b9186279..9a9cbed0a30 100644 --- a/yt/yt/client/chunk_client/data_statistics.h +++ b/yt/yt/client/chunk_client/data_statistics.h @@ -12,6 +12,8 @@ #include <library/cpp/yt/small_containers/compact_flat_map.h> +#include <library/cpp/yt/string/format.h> + namespace NYT::NChunkClient { //////////////////////////////////////////////////////////////////////////////// @@ -69,7 +71,6 @@ private: }; void FormatValue(TStringBuilderBase* builder, const TCodecStatistics& statistics, TStringBuf spec); -TString ToString(const TCodecStatistics& statistics); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/chunk_client/read_limit.cpp b/yt/yt/client/chunk_client/read_limit.cpp index 3d106043885..e862c18a54f 100644 --- a/yt/yt/client/chunk_client/read_limit.cpp +++ b/yt/yt/client/chunk_client/read_limit.cpp @@ -288,6 +288,11 @@ TString ToString(const TLegacyReadLimit& limit) return builder.Flush(); } +void FormatValue(TStringBuilderBase* builder, const TLegacyReadLimit& limit, TStringBuf spec) +{ + FormatValue(builder, ToString(limit), spec); +} + bool IsTrivial(const TLegacyReadLimit& limit) { return limit.IsTrivial(); @@ -438,9 +443,9 @@ void TLegacyReadRange::InitMove(NProto::TReadRange&& range) //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TLegacyReadRange& range) +void FormatValue(TStringBuilderBase* builder, const TLegacyReadRange& range, TStringBuf spec) { - return Format("[<%v> : <%v>]", range.LowerLimit(), range.UpperLimit()); + FormatValue(builder, Format("[<%v> : <%v>]", range.LowerLimit(), range.UpperLimit()), spec); } void ToProto(NProto::TReadRange* protoReadRange, const TLegacyReadRange& readRange) @@ -725,6 +730,11 @@ TString ToString(const TReadLimit& readLimit) return builder.Flush(); } +void FormatValue(TStringBuilderBase* builder, const TReadLimit& readLimit, TStringBuf spec) +{ + FormatValue(builder, ToString(readLimit), spec); +} + void ToProto(NProto::TReadLimit* protoReadLimit, const TReadLimit& readLimit) { if (readLimit.KeyBound()) { @@ -888,9 +898,9 @@ TReadRange TReadRange::MakeEmpty() //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TReadRange& readRange) +void FormatValue(TStringBuilderBase* builder, const TReadRange& readRange, TStringBuf spec) { - return Format("[<%v> : <%v>]", readRange.LowerLimit(), readRange.UpperLimit()); + FormatValue(builder, Format("[<%v> : <%v>]", readRange.LowerLimit(), readRange.UpperLimit()), spec); } void ToProto(NProto::TReadRange* protoReadRange, const TReadRange& readRange) diff --git a/yt/yt/client/chunk_client/read_limit.h b/yt/yt/client/chunk_client/read_limit.h index 0ce33e3b0ee..5faa08a654e 100644 --- a/yt/yt/client/chunk_client/read_limit.h +++ b/yt/yt/client/chunk_client/read_limit.h @@ -81,6 +81,7 @@ private: //////////////////////////////////////////////////////////////////////////////// TString ToString(const TLegacyReadLimit& limit); +void FormatValue(TStringBuilderBase* builder, const TLegacyReadLimit& limit, TStringBuf spec); bool IsTrivial(const TLegacyReadLimit& limit); bool IsTrivial(const NProto::TReadLimit& limit); @@ -117,7 +118,7 @@ private: //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TLegacyReadRange& range); +void FormatValue(TStringBuilderBase* builder, const TLegacyReadRange& range, TStringBuf spec); void ToProto(NProto::TReadRange* protoReadRange, const TLegacyReadRange& readRange); void FromProto(TLegacyReadRange* readRange, const NProto::TReadRange& protoReadRange); @@ -180,6 +181,7 @@ public: //////////////////////////////////////////////////////////////////////////////// TString ToString(const TReadLimit& readLimit); +void FormatValue(TStringBuilderBase* builder, const TReadLimit& readLimit, TStringBuf spec); void ToProto(NProto::TReadLimit* protoReadLimit, const TReadLimit& readLimit); //! If protoReadLimit contains key, it is transformed into new key bound by @@ -214,7 +216,7 @@ public: //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TReadRange& readRange); +void FormatValue(TStringBuilderBase* builder, const TReadRange& readRange, TStringBuf spec); void ToProto(NProto::TReadRange* protoReadRange, const TReadRange& readRange); //! See comment for FromProto(TReadLimit*, const NProto::TReadLimit&, bool, std::optional<int>). diff --git a/yt/yt/client/election/public.h b/yt/yt/client/election/public.h index 7b79682b48e..baf25c87727 100644 --- a/yt/yt/client/election/public.h +++ b/yt/yt/client/election/public.h @@ -5,6 +5,8 @@ #include <library/cpp/yt/misc/enum.h> #include <library/cpp/yt/misc/guid.h> +#include <library/cpp/yt/string/guid.h> + namespace NYT::NElection { //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/query_client/query_statistics.cpp b/yt/yt/client/query_client/query_statistics.cpp index fd2b30b651e..1ae041a9952 100644 --- a/yt/yt/client/query_client/query_statistics.cpp +++ b/yt/yt/client/query_client/query_statistics.cpp @@ -59,9 +59,10 @@ void FromProto(TQueryStatistics* original, const NProto::TQueryStatistics& seria FromProto(&original->InnerStatistics, serialized.inner_statistics()); } -TString ToString(const TQueryStatistics& stats) +void FormatValue(TStringBuilderBase* builder, const TQueryStatistics& stats, TStringBuf /*spec*/) { - return Format( + Format( + builder, "{" "RowsRead: %v, DataWeightRead: %v, RowsWritten: %v, " "SyncTime: %v, AsyncTime: %v, ExecuteTime: %v, ReadTime: %v, WriteTime: %v, CodegenTime: %v, " diff --git a/yt/yt/client/query_client/query_statistics.h b/yt/yt/client/query_client/query_statistics.h index 7f9464e5612..a759a09372e 100644 --- a/yt/yt/client/query_client/query_statistics.h +++ b/yt/yt/client/query_client/query_statistics.h @@ -32,7 +32,7 @@ struct TQueryStatistics void ToProto(NProto::TQueryStatistics* serialized, const TQueryStatistics& original); void FromProto(TQueryStatistics* original, const NProto::TQueryStatistics& serialized); -TString ToString(const TQueryStatistics& stat); +void FormatValue(TStringBuilderBase* builder, const TQueryStatistics& stat, TStringBuf spec); void Serialize(const TQueryStatistics& statistics, NYson::IYsonConsumer* consumer); diff --git a/yt/yt/client/queue_client/common.cpp b/yt/yt/client/queue_client/common.cpp index 22beeb98bbf..d054f2c34c8 100644 --- a/yt/yt/client/queue_client/common.cpp +++ b/yt/yt/client/queue_client/common.cpp @@ -60,9 +60,9 @@ TCrossClusterReference TCrossClusterReference::FromRichYPath(const TRichYPath& p }; } -TString ToString(const TCrossClusterReference& queueRef) +void FormatValue(TStringBuilderBase* builder, const TCrossClusterReference& queueRef, TStringBuf spec) { - return Format("%v:%v", queueRef.Cluster, queueRef.Path); + FormatValue(builder, Format("%v:%v", queueRef.Cluster, queueRef.Path), spec); } void Serialize(const TCrossClusterReference& queueRef, IYsonConsumer* consumer) diff --git a/yt/yt/client/queue_client/common.h b/yt/yt/client/queue_client/common.h index 93e5bc409e2..94030174109 100644 --- a/yt/yt/client/queue_client/common.h +++ b/yt/yt/client/queue_client/common.h @@ -25,7 +25,7 @@ struct TCrossClusterReference static TCrossClusterReference FromRichYPath(const NYPath::TRichYPath& path); }; -TString ToString(const TCrossClusterReference& queueRef); +void FormatValue(TStringBuilderBase* builder, const TCrossClusterReference& queueRef, TStringBuf spec); void Serialize(const TCrossClusterReference& queueRef, NYson::IYsonConsumer* consumer); diff --git a/yt/yt/client/table_client/config.cpp b/yt/yt/client/table_client/config.cpp index 32a8317feb7..1ed6989885f 100644 --- a/yt/yt/client/table_client/config.cpp +++ b/yt/yt/client/table_client/config.cpp @@ -20,12 +20,14 @@ using namespace NChunkClient; //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TRetentionConfigPtr& obj) +void FormatValue(TStringBuilderBase* builder, const TRetentionConfigPtr& obj, TStringBuf spec) { - static const TString NullPtrName("<nullptr>"); - return obj - ? NYson::ConvertToYsonString(obj, NYson::EYsonFormat::Text).ToString() - : NullPtrName; + static const TStringBuf NullPtrName("<nullptr>"); + if (!obj) { + FormatValue(builder, NullPtrName, spec); + return; + } + FormatValue(builder, NYson::ConvertToYsonString(obj, NYson::EYsonFormat::Text), spec); } //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/table_client/config.h b/yt/yt/client/table_client/config.h index 56e4ccfa1cb..9423012fa43 100644 --- a/yt/yt/client/table_client/config.h +++ b/yt/yt/client/table_client/config.h @@ -33,7 +33,7 @@ public: DEFINE_REFCOUNTED_TYPE(TRetentionConfig) -TString ToString(const TRetentionConfigPtr& obj); +void FormatValue(TStringBuilderBase* builder, const TRetentionConfigPtr& obj, TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/table_client/logical_type.cpp b/yt/yt/client/table_client/logical_type.cpp index 2fec0be07e4..4a677262fa2 100644 --- a/yt/yt/client/table_client/logical_type.cpp +++ b/yt/yt/client/table_client/logical_type.cpp @@ -286,6 +286,12 @@ TString ToString(const TLogicalType& logicalType) YT_ABORT(); } +void FormatValue(TStringBuilderBase* builder, const TLogicalType& logicalType, TStringBuf spec) +{ + // TODO(arkady-e1ppa): Optimize and express ToString using this. + FormatValue(builder, ToString(logicalType), spec); +} + void PrintTo(ELogicalMetatype metatype, std::ostream* os) { *os << ToString(metatype); diff --git a/yt/yt/client/table_client/logical_type.h b/yt/yt/client/table_client/logical_type.h index 63750956f2c..b7db1a8143a 100644 --- a/yt/yt/client/table_client/logical_type.h +++ b/yt/yt/client/table_client/logical_type.h @@ -104,6 +104,7 @@ private: DEFINE_REFCOUNTED_TYPE(TLogicalType) TString ToString(const TLogicalType& logicalType); +void FormatValue(TStringBuilderBase* builder, const TLogicalType& logicalType, TStringBuf /*spec*/); //! Debug printers for Gtest unittests. void PrintTo(ELogicalMetatype type, std::ostream* os); diff --git a/yt/yt/client/table_client/row_base.cpp b/yt/yt/client/table_client/row_base.cpp index b2970d58d6d..16703b8fc30 100644 --- a/yt/yt/client/table_client/row_base.cpp +++ b/yt/yt/client/table_client/row_base.cpp @@ -168,12 +168,12 @@ void ValidateColumnFilter(const TColumnFilter& columnFilter, int schemaColumnCou //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TColumnFilter& columnFilter) +void FormatValue(TStringBuilderBase* builder, const TColumnFilter& columnFilter, TStringBuf spec) { if (columnFilter.IsUniversal()) { - return TString("{All}"); + FormatValue(builder, TStringBuf{"{All}"}, spec); } else { - return Format("{%v}", columnFilter.GetIndexes()); + FormatValue(builder, Format("{%v}", columnFilter.GetIndexes()), spec); } } diff --git a/yt/yt/client/table_client/row_base.h b/yt/yt/client/table_client/row_base.h index fcbb675a397..1907e7082f4 100644 --- a/yt/yt/client/table_client/row_base.h +++ b/yt/yt/client/table_client/row_base.h @@ -336,7 +336,7 @@ private: //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TColumnFilter& columnFilter); +void FormatValue(TStringBuilderBase* builder, const TColumnFilter& columnFilter, TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/client/table_client/schema.cpp b/yt/yt/client/table_client/schema.cpp index d815e0d58ea..1e3637fb0b8 100644 --- a/yt/yt/client/table_client/schema.cpp +++ b/yt/yt/client/table_client/schema.cpp @@ -1376,11 +1376,6 @@ void FormatValue(TStringBuilderBase* builder, const TTableSchema& schema, TStrin builder->AppendChar(']'); } -TString ToString(const TTableSchema& schema) -{ - return ToStringViaBuilder(schema); -} - void FormatValue(TStringBuilderBase* builder, const TTableSchemaPtr& schema, TStringBuf spec) { if (schema) { @@ -1390,11 +1385,6 @@ void FormatValue(TStringBuilderBase* builder, const TTableSchemaPtr& schema, TSt } } -TString ToString(const TTableSchemaPtr& schema) -{ - return ToStringViaBuilder(schema); -} - TString SerializeToWireProto(const TTableSchemaPtr& schema) { NTableClient::NProto::TTableSchemaExt protoSchema; diff --git a/yt/yt/client/table_client/schema.h b/yt/yt/client/table_client/schema.h index 9c6e7940726..d5bb5a37aae 100644 --- a/yt/yt/client/table_client/schema.h +++ b/yt/yt/client/table_client/schema.h @@ -422,9 +422,6 @@ DEFINE_REFCOUNTED_TYPE(TTableSchema) void FormatValue(TStringBuilderBase* builder, const TTableSchema& schema, TStringBuf spec); void FormatValue(TStringBuilderBase* builder, const TTableSchemaPtr& schema, TStringBuf spec); -TString ToString(const TTableSchema& schema); -TString ToString(const TTableSchemaPtr& schema); - //! Returns serialized NTableClient.NProto.TTableSchemaExt. TString SerializeToWireProto(const TTableSchemaPtr& schema); diff --git a/yt/yt/client/ypath/rich.cpp b/yt/yt/client/ypath/rich.cpp index ab78d81171f..2336220f63f 100644 --- a/yt/yt/client/ypath/rich.cpp +++ b/yt/yt/client/ypath/rich.cpp @@ -695,6 +695,11 @@ TString ToString(const TRichYPath& path) return ConvertToString(path, EYsonFormat::Text); } +void FormatValue(TStringBuilderBase* builder, const TRichYPath& path, TStringBuf spec) +{ + FormatValue(builder, ToString(path), spec); +} + std::vector<TRichYPath> Normalize(const std::vector<TRichYPath>& paths) { std::vector<TRichYPath> result; diff --git a/yt/yt/client/ypath/rich.h b/yt/yt/client/ypath/rich.h index d74f0609c6e..e1dc440db95 100644 --- a/yt/yt/client/ypath/rich.h +++ b/yt/yt/client/ypath/rich.h @@ -177,6 +177,7 @@ bool operator== (const TRichYPath& lhs, const TRichYPath& rhs); //////////////////////////////////////////////////////////////////////////////// TString ToString(const TRichYPath& path); +void FormatValue(TStringBuilderBase* builder, const TRichYPath& path, TStringBuf spec); std::vector<TRichYPath> Normalize(const std::vector<TRichYPath>& paths); diff --git a/yt/yt/core/concurrency/fiber.cpp b/yt/yt/core/concurrency/fiber.cpp index 3c7b72b5f53..1b0717fa268 100644 --- a/yt/yt/core/concurrency/fiber.cpp +++ b/yt/yt/core/concurrency/fiber.cpp @@ -233,7 +233,6 @@ private: auto* regNode = static_cast<TIntrusiveListItem<NDetail::TFiberBase, NDetail::TFiberRegisterTag>*>(fiber); auto* delNode = static_cast<TIntrusiveListItem<NDetail::TFiberBase, NDetail::TFiberUnregisterTag>*>(fiber); - Cerr << Format("Fiber node at %v", iter) << '\n'; Cerr << Format("Fiber address after cast is %v", fiber) << '\n'; Cerr << Format("Fiber registration queue status: Next: %v, Prev: %v", regNode->Next(), regNode->Prev()) << '\n'; // NB: Reading deletion queue is data race. Don't do this under tsan. diff --git a/yt/yt/core/logging/file_log_writer.cpp b/yt/yt/core/logging/file_log_writer.cpp index 89d9b50d932..371866c55a6 100644 --- a/yt/yt/core/logging/file_log_writer.cpp +++ b/yt/yt/core/logging/file_log_writer.cpp @@ -286,7 +286,7 @@ private: int width = ToString(ssize(fileNames)).length(); TString formatString = "%v.%0" + ToString(width) + "d"; for (int index = ssize(fileNames); index > 0; --index) { - auto newFileName = Format(formatString, FileNamePrefix_, index); + auto newFileName = Format(TRuntimeFormat{formatString}, FileNamePrefix_, index); auto oldPath = NFS::CombinePaths(DirectoryName_, fileNames[index - 1]); auto newPath = NFS::CombinePaths(DirectoryName_, newFileName); YT_LOG_DEBUG("Rename log segment (OldFilePath: %v, NewFilePath: %v)", oldPath, newPath); diff --git a/yt/yt/core/misc/error-inl.h b/yt/yt/core/misc/error-inl.h index d147a3dd9a7..79dc4108f92 100644 --- a/yt/yt/core/misc/error-inl.h +++ b/yt/yt/core/misc/error-inl.h @@ -55,7 +55,7 @@ namespace NDetail { template <size_t Length, class... TArgs> TString FormatErrorMessage(const char (&format)[Length], TArgs&&... args) { - return Format(format, std::forward<TArgs>(args)...); + return Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...); } template <size_t Length> diff --git a/yt/yt/core/misc/error_code.cpp b/yt/yt/core/misc/error_code.cpp index 7178d248038..f713cc9c8a5 100644 --- a/yt/yt/core/misc/error_code.cpp +++ b/yt/yt/core/misc/error_code.cpp @@ -146,17 +146,24 @@ TString TErrorCodeRegistry::ParseNamespace(const std::type_info& errorCodeEnumTy return name; } -TString ToString(const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo) +void FormatValue( + TStringBuilderBase* builder, + const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo, + TStringBuf /*spec*/) { if (errorCodeInfo.Namespace.empty()) { - return Format("EErrorCode::%v", errorCodeInfo.Name); + Format(builder, "EErrorCode::%v", errorCodeInfo.Name); + return; } - return Format("%v::EErrorCode::%v", errorCodeInfo.Namespace, errorCodeInfo.Name); + Format(builder, "%v::EErrorCode::%v", errorCodeInfo.Namespace, errorCodeInfo.Name); } -TString ToString(const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeRangeInfo) +void FormatValue( + TStringBuilderBase* builder, + const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeRangeInfo, + TStringBuf /*spec*/) { - return Format("%v-%v", errorCodeRangeInfo.From, errorCodeRangeInfo.To); + Format(builder, "%v-%v", errorCodeRangeInfo.From, errorCodeRangeInfo.To); } //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/misc/error_code.h b/yt/yt/core/misc/error_code.h index 3ab42c092a4..2ab243522ea 100644 --- a/yt/yt/core/misc/error_code.h +++ b/yt/yt/core/misc/error_code.h @@ -3,6 +3,8 @@ #include <library/cpp/yt/misc/enum.h> #include <library/cpp/yt/misc/port.h> +#include <library/cpp/yt/string/format.h> + #include <util/generic/hash_set.h> #include <library/cpp/yt/misc/preprocessor.h> @@ -61,8 +63,15 @@ private: void CheckCodesAgainstRanges() const; }; -TString ToString(const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo); -TString ToString(const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeInfo); +void FormatValue( + TStringBuilderBase* builder, + const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo, + TStringBuf spec); + +void FormatValue( + TStringBuilderBase* builder, + const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeInfo, + TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/misc/protobuf_helpers-inl.h b/yt/yt/core/misc/protobuf_helpers-inl.h index 2dd1757eeaf..adf5a308518 100644 --- a/yt/yt/core/misc/protobuf_helpers-inl.h +++ b/yt/yt/core/misc/protobuf_helpers-inl.h @@ -572,28 +572,6 @@ i64 TRefCountedProto<TProto>::GetSize() const //////////////////////////////////////////////////////////////////////////////// -// RepeatedField formatter -template <class T> -struct TValueFormatter<::google::protobuf::RepeatedField<T>> -{ - static void Do(TStringBuilderBase* builder, const ::google::protobuf::RepeatedField<T>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; - -// RepeatedPtrField formatter -template <class T> -struct TValueFormatter<::google::protobuf::RepeatedPtrField<T>> -{ - static void Do(TStringBuilderBase* builder, const ::google::protobuf::RepeatedPtrField<T>& collection, TStringBuf /*format*/) - { - FormatRange(builder, collection, TDefaultFormatter()); - } -}; - -//////////////////////////////////////////////////////////////////////////////// - template <class TSerialized, class T, class TTag> void FromProto(TStrongTypedef<T, TTag>* original, const TSerialized& serialized) { @@ -608,4 +586,38 @@ void ToProto(TSerialized* serialized, const TStrongTypedef<T, TTag>& original) //////////////////////////////////////////////////////////////////////////////// +template <std::derived_from<::google::protobuf::MessageLite> T> +void FormatValue(TStringBuilderBase* builder, const T& message, TStringBuf spec) +{ + FormatValue(builder, NYT::ToStringIgnoringFormatValue(message), spec); +} + +//////////////////////////////////////////////////////////////////////////////// + } // namespace NYT + +namespace google::protobuf { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void FormatValue( + NYT::TStringBuilderBase* builder, + const ::google::protobuf::RepeatedField<T>& collection, + TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, NYT::TDefaultFormatter()); +} + +template <class T> +void FormatValue( + NYT::TStringBuilderBase* builder, + const ::google::protobuf::RepeatedPtrField<T>& collection, + TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, NYT::TDefaultFormatter()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace google::protobuf diff --git a/yt/yt/core/misc/protobuf_helpers.h b/yt/yt/core/misc/protobuf_helpers.h index 70e163c7a7f..c20ace80177 100644 --- a/yt/yt/core/misc/protobuf_helpers.h +++ b/yt/yt/core/misc/protobuf_helpers.h @@ -214,6 +214,11 @@ TSharedRef PopEnvelope(const TSharedRef& data); //////////////////////////////////////////////////////////////////////////////// +template <std::derived_from<::google::protobuf::MessageLite> T> +void FormatValue(TStringBuilderBase* builder, const T& message, TStringBuf /*spec*/); + +//////////////////////////////////////////////////////////////////////////////// + struct TBinaryProtoSerializer { //! Serializes a given protobuf message into a given stream. @@ -466,6 +471,26 @@ public: } // namespace NYT +namespace google::protobuf { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void FormatValue( + NYT::TStringBuilderBase* builder, + const ::google::protobuf::RepeatedField<T>& collection, + TStringBuf /*spec*/); + +template <class T> +void FormatValue( + NYT::TStringBuilderBase* builder, + const ::google::protobuf::RepeatedPtrField<T>& collection, + TStringBuf /*spec*/); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace google::protobuf + #define PROTOBUF_HELPERS_INL_H_ #include "protobuf_helpers-inl.h" #undef PROTOBUF_HELPERS_INL_H_ diff --git a/yt/yt/core/misc/range_formatters-inl.h b/yt/yt/core/misc/range_formatters-inl.h new file mode 100644 index 00000000000..7dba39874f1 --- /dev/null +++ b/yt/yt/core/misc/range_formatters-inl.h @@ -0,0 +1,25 @@ +#ifndef RANGE_FORMATTERS_INL_H_ +#error "Direct inclusion of this file is not allowed, include range_formatters.h" +// For the sake of sane code completion. +#include "range_formatters.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TRange<T>& collection, TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} + +template <class T> +void FormatValue(TStringBuilderBase* builder, const TSharedRange<T>& collection, TStringBuf /*spec*/) +{ + NYT::FormatRange(builder, collection, TDefaultFormatter()); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/yt/core/misc/range_formatters.h b/yt/yt/core/misc/range_formatters.h index 4bfdd13a2c4..6cdfa33bf9f 100644 --- a/yt/yt/core/misc/range_formatters.h +++ b/yt/yt/core/misc/range_formatters.h @@ -9,26 +9,16 @@ namespace NYT { //////////////////////////////////////////////////////////////////////////////// -// TRange formatter template <class T> -struct TValueFormatter<TRange<T>, void> -{ - static void Do(TStringBuilderBase* builder, TRange<T> range, TStringBuf /*format*/) - { - FormatRange(builder, range, TDefaultFormatter()); - } -}; +void FormatValue(TStringBuilderBase* builder, const TRange<T>& collection, TStringBuf /*spec*/); -// TSharedRange formatter template <class T> -struct TValueFormatter<TSharedRange<T>> -{ - static void Do(TStringBuilderBase* builder, const TSharedRange<T>& range, TStringBuf /*format*/) - { - FormatRange(builder, range, TDefaultFormatter()); - } -}; +void FormatValue(TStringBuilderBase* builder, const TSharedRange<T>& collection, TStringBuf /*spec*/); //////////////////////////////////////////////////////////////////////////////// } // namespace NYT + +#define RANGE_FORMATTERS_INL_H_ +#include "range_formatters-inl.h" +#undef RANGE_FORMATTERS_INL_H_ diff --git a/yt/yt/core/misc/serialize_dump.h b/yt/yt/core/misc/serialize_dump.h index 7581032bee4..6a01444638d 100644 --- a/yt/yt/core/misc/serialize_dump.h +++ b/yt/yt/core/misc/serialize_dump.h @@ -184,7 +184,7 @@ struct TSerializationDumpPodWriter template <class C> static void Do(C& context, const T& value) { - if constexpr(TFormatTraits<T>::HasCustomFormatValue) { + if constexpr(CFormattable<T>) { SERIALIZATION_DUMP_WRITE(context, "pod %v", value); } else { SERIALIZATION_DUMP_WRITE(context, "pod[%v] %v", sizeof(T), DumpRangeToHex(TRef::FromPod(value))); diff --git a/yt/yt/core/misc/unittests/format_ut.cpp b/yt/yt/core/misc/unittests/format_ut.cpp index cfbce436564..d8c7834e056 100644 --- a/yt/yt/core/misc/unittests/format_ut.cpp +++ b/yt/yt/core/misc/unittests/format_ut.cpp @@ -9,7 +9,7 @@ namespace { //////////////////////////////////////////////////////////////////////////////// -static_assert(!TFormatTraits<TIntrusivePtr<TRefCounted>>::HasCustomFormatValue); +static_assert(!CFormattable<TIntrusivePtr<TRefCounted>>); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/net/address.cpp b/yt/yt/core/net/address.cpp index 2f05fe40dfd..3044ddeb4fb 100644 --- a/yt/yt/core/net/address.cpp +++ b/yt/yt/core/net/address.cpp @@ -388,6 +388,12 @@ void FromProto(TNetworkAddress* address, const TString& protoAddress) address->Length_ = protoAddress.size(); } +void FormatValue(TStringBuilderBase* builder, const TNetworkAddress& address, TStringBuf spec) +{ + // TODO(arkady-e1ppa): Optimize. + FormatValue(builder, ToString(address), spec); +} + TString ToString(const TNetworkAddress& address, const TNetworkAddressFormatOptions& options) { const auto& sockAddr = address.GetSockAddr(); diff --git a/yt/yt/core/net/address.h b/yt/yt/core/net/address.h index 37626d5d234..9e139c5af4f 100644 --- a/yt/yt/core/net/address.h +++ b/yt/yt/core/net/address.h @@ -93,6 +93,7 @@ struct TNetworkAddressFormatOptions bool IncludeTcpProtocol = true; }; +void FormatValue(TStringBuilderBase* builder, const TNetworkAddress& address, TStringBuf spec); TString ToString(const TNetworkAddress& address, const TNetworkAddressFormatOptions& options = {}); bool operator == (const TNetworkAddress& lhs, const TNetworkAddress& rhs); diff --git a/yt/yt/core/rpc/service-inl.h b/yt/yt/core/rpc/service-inl.h index 501bf06a66c..3b60e42e72a 100644 --- a/yt/yt/core/rpc/service-inl.h +++ b/yt/yt/core/rpc/service-inl.h @@ -14,7 +14,7 @@ template <class... TArgs> void IServiceContext::SetRequestInfo(const char* format, TArgs&&... args) { if (IsLoggingEnabled()) { - SetRawRequestInfo(Format(format, std::forward<TArgs>(args)...), /*incremental*/ false); + SetRawRequestInfo(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...), /*incremental*/ false); } else { SuppressMissingRequestInfoCheck(); } @@ -24,7 +24,7 @@ template <class... TArgs> void IServiceContext::SetIncrementalRequestInfo(const char* format, TArgs&&... args) { if (IsLoggingEnabled()) { - SetRawRequestInfo(Format(format, std::forward<TArgs>(args)...), /*incremental*/ true); + SetRawRequestInfo(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...), /*incremental*/ true); } else { SuppressMissingRequestInfoCheck(); } @@ -34,7 +34,7 @@ template <class... TArgs> void IServiceContext::SetResponseInfo(const char* format, TArgs&&... args) { if (IsLoggingEnabled()) { - SetRawResponseInfo(Format(format, std::forward<TArgs>(args)...), /*incremental*/ false); + SetRawResponseInfo(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...), /*incremental*/ false); } } @@ -42,7 +42,7 @@ template <class... TArgs> void IServiceContext::SetIncrementalResponseInfo(const char* format, TArgs&&... args) { if (IsLoggingEnabled()) { - SetRawResponseInfo(Format(format, std::forward<TArgs>(args)...), /*incremental*/ true); + SetRawResponseInfo(Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...), /*incremental*/ true); } } diff --git a/yt/yt/core/yson/token.cpp b/yt/yt/core/yson/token.cpp index 7d486d83c79..3a8ad50964b 100644 --- a/yt/yt/core/yson/token.cpp +++ b/yt/yt/core/yson/token.cpp @@ -205,29 +205,37 @@ void TToken::Reset() BooleanValue_ = false; } -TString ToString(const TToken& token) +// TODO(arkady-e1ppa): Consider doing ToString anyway (e.g. with spec = 'v') +// and then applying spec to the string value. +void FormatValue(TStringBuilderBase* builder, const TToken& token, TStringBuf spec) { switch (token.GetType()) { case ETokenType::EndOfStream: - return TString(); + FormatValue(builder, TStringBuf{}, spec); + break; case ETokenType::String: - return TString(token.GetStringValue()); + FormatValue(builder, token.GetStringValue(), spec); + break; case ETokenType::Int64: - return ::ToString(token.GetInt64Value()); + FormatValue(builder, token.GetInt64Value(), spec); + break; case ETokenType::Uint64: - return ::ToString(token.GetUint64Value()); + FormatValue(builder, token.GetUint64Value(), spec); + break; case ETokenType::Double: - return ::ToString(token.GetDoubleValue()); + FormatValue(builder, token.GetDoubleValue(), spec); + break; case ETokenType::Boolean: - return TString(FormatBool(token.GetBooleanValue())); + FormatValue(builder, FormatBool(token.GetBooleanValue()), spec); + break; default: - return TokenTypeToString(token.GetType()); + FormatValue(builder, TokenTypeToString(token.GetType()), spec); } } diff --git a/yt/yt/core/yson/token.h b/yt/yt/core/yson/token.h index c9da4c92729..66c3e91075b 100644 --- a/yt/yt/core/yson/token.h +++ b/yt/yt/core/yson/token.h @@ -84,7 +84,7 @@ private: bool BooleanValue_ = false; }; -TString ToString(const TToken& token); +void FormatValue(TStringBuilderBase* builder, const TToken& token, TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/library/numeric/serialize/double_array.h b/yt/yt/library/numeric/serialize/double_array.h index a700b4d0ab7..9991d814310 100644 --- a/yt/yt/library/numeric/serialize/double_array.h +++ b/yt/yt/library/numeric/serialize/double_array.h @@ -9,19 +9,17 @@ namespace NYT { //////////////////////////////////////////////////////////////////////////////// template <class TDerived> -struct TValueFormatter<TDerived, std::enable_if_t<IsDoubleArray<TDerived>>> + requires (IsDoubleArray<TDerived>) +void FormatValue(TStringBuilderBase* builder, const TDerived& vec, TStringBuf spec) { - static void Do(TStringBuilderBase* builder, const TDerived& vec, TStringBuf format) - { - builder->AppendChar('['); - FormatValue(builder, vec[0], format); - for (size_t i = 1; i < TDerived::Size; i++) { - builder->AppendChar(' '); - FormatValue(builder, vec[i], format); - } - builder->AppendChar(']'); + builder->AppendChar('['); + FormatValue(builder, vec[0], spec); + for (size_t i = 1; i < TDerived::Size; i++) { + builder->AppendChar(' '); + FormatValue(builder, vec[i], spec); } -}; + builder->AppendChar(']'); +} // TODO(ignat) // template <class TDerived, class = std::enable_if_t<IsDoubleArray<TDerived>>> diff --git a/yt/yt/library/process/pipe.cpp b/yt/yt/library/process/pipe.cpp index 6584b75b5db..e4051f6604f 100644 --- a/yt/yt/library/process/pipe.cpp +++ b/yt/yt/library/process/pipe.cpp @@ -208,11 +208,20 @@ void TPipe::CloseWriteFD() //////////////////////////////////////////////////////////////////////////////// -TString ToString(const TPipe& pipe) +void FormatValue(TStringBuilderBase* builder, const TPipe& pipe, TStringBuf spec) { - return Format("{ReadFD: %v, WriteFD: %v}", - pipe.GetReadFD(), - pipe.GetWriteFD()); + // TODO(arkady-e1ppa): We format pipe twice + // (pipe itself and its serialization) + // This is probably redundant. + // Check if it is later and remove + // the second step. + FormatValue( + builder, + Format( + "{ReadFD: %v, WriteFD: %v}", + pipe.GetReadFD(), + pipe.GetWriteFD()), + spec); } //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/library/process/pipe.h b/yt/yt/library/process/pipe.h index 1a827ac977d..b4d1b3916d8 100644 --- a/yt/yt/library/process/pipe.h +++ b/yt/yt/library/process/pipe.h @@ -91,7 +91,7 @@ private: friend class TPipeFactory; }; -TString ToString(const TPipe& pipe); +void FormatValue(TStringBuilderBase* builder, const TPipe& pipe, TStringBuf spec); //////////////////////////////////////////////////////////////////////////////// |
