Skip to content

Commit

Permalink
Improve compile-time checks
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed May 19, 2021
1 parent 21d93bf commit e9c1c41
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 298 deletions.
100 changes: 24 additions & 76 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1642,27 +1642,6 @@ constexpr format_arg_store<Context, Args...> make_format_args(
return {args...};
}

/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``format_str`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& format_str,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(format_str);
return {args...};
}

/**
\rst
Returns a named argument to be used in a formatting function.
Expand Down Expand Up @@ -2668,49 +2647,9 @@ void check_format_string(S format_str) {
(void)invalid_format;
}

// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}

#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()

/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )

template <typename Char>
void vformat_to(
buffer<type_identity_t<Char>>& buf, basic_string_view<Char> format_str,
buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args,
detail::locale_ref loc = {});

Expand All @@ -2730,8 +2669,6 @@ struct formatter<T, Char,
detail::dynamic_format_specs<Char> specs_;

public:
FMT_CONSTEXPR formatter() = default;

// Parses format specifiers stopping either at the end of the range or at the
// terminating '}'.
template <typename ParseContext>
Expand Down Expand Up @@ -2802,43 +2739,54 @@ struct formatter<T, Char,
-> decltype(ctx.out());
};

template <typename Char> struct basic_runtime { basic_string_view<Char> str; };

template <typename Char, typename... Args> class basic_format_string {
private:
basic_string_view<Char> str_;

public:
#if FMT_COMPILE_TIME_CHECKS
template <size_t N>
consteval basic_format_string(const char (&s)[N]) : str_(s) {
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(string_view(s, N), checker(s, {}));
}
}
#endif

template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
basic_format_string(const S& s) : str_(s) {
#if FMT_COMPILE_TIME_CHECKS
consteval
#endif
basic_format_string(const S& s)
: str_(s) {
static_assert(
detail::count<
(std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
#if FMT_COMPILE_TIME_CHECKS
if constexpr (detail::count_named_args<Args...>() == 0) {
using checker = detail::format_string_checker<Char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(str_, checker(s, {}));
}
#else
detail::check_format_string<Args...>(s);
#endif
}
basic_format_string(basic_runtime<Char> r) : str_(r.str) {}

FMT_INLINE operator basic_string_view<Char>() const { return str_; }
};

#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
template <typename... Args> using format_string = string_view;
template <typename S> auto runtime(const S& s) -> basic_string_view<char_t<S>> {
return s;
}
#else
template <typename... Args>
using format_string = basic_format_string<char, type_identity_t<Args>...>;
// Creates a runtime format string.
template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
return {{s}};
}
#endif

FMT_API auto vformat(string_view fmt, format_args args) -> std::string;
Expand Down
81 changes: 68 additions & 13 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -2143,6 +2143,46 @@ FMT_CONSTEXPR void handle_dynamic_spec(int& value,
}
}

// Converts a compile-time string to basic_string_view.
template <typename Char, size_t N>
constexpr auto compile_string_to_view(const Char (&s)[N])
-> basic_string_view<Char> {
// Remove trailing NUL character if needed. Won't be present if this is used
// with a raw character array (i.e. not defined as a string).
return {s, N - (std::char_traits<Char>::to_int_type(s[N - 1]) == 0 ? 1 : 0)};
}
template <typename Char>
constexpr auto compile_string_to_view(std_string_view<Char> s)
-> basic_string_view<Char> {
return {s.data(), s.size()};
}

#define FMT_STRING_IMPL(s, base, explicit) \
[] { \
/* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \
/* Use a macro-like name to avoid shadowing warnings. */ \
struct FMT_GCC_VISIBILITY_HIDDEN FMT_COMPILE_STRING : base { \
using char_type = fmt::remove_cvref_t<decltype(s[0])>; \
FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \
operator fmt::basic_string_view<char_type>() const { \
return fmt::detail::compile_string_to_view<char_type>(s); \
} \
}; \
return FMT_COMPILE_STRING(); \
}()

/**
\rst
Constructs a compile-time format string from a string literal *s*.
**Example**::
// A compile-time error because 'd' is an invalid specifier for strings.
std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
\endrst
*/
#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::compile_string, )

using format_func = void (*)(detail::buffer<char>&, int, const char*);

FMT_API void format_error_code(buffer<char>& out, int error_code,
Expand Down Expand Up @@ -2573,22 +2613,20 @@ FMT_MODULE_EXPORT_END

template <typename Char>
void detail::vformat_to(
detail::buffer<type_identity_t<Char>>& buf,
basic_string_view<Char> format_str,
detail::buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffer_context<type_identity_t<Char>>> args,
detail::locale_ref loc) {
using iterator = typename buffer_context<Char>::iterator;
auto out = buffer_appender<Char>(buf);
if (format_str.size() == 2 && equal2(format_str.data(), "{}")) {
if (fmt.size() == 2 && equal2(fmt.data(), "{}")) {
auto arg = args.get(0);
if (!arg) error_handler().on_error("argument not found");
visit_format_arg(default_arg_formatter<iterator, Char>{out, args, loc},
arg);
return;
}
format_handler<iterator, Char, buffer_context<Char>> h(out, format_str, args,
loc);
parse_format_string<false>(format_str, h);
format_handler<iterator, Char, buffer_context<Char>> h(out, fmt, args, loc);
parse_format_string<false>(fmt, h);
}

#ifndef FMT_HEADER_ONLY
Expand Down Expand Up @@ -2630,6 +2668,27 @@ template <typename OutputIt, typename Char = char>
using format_args_t FMT_DEPRECATED_ALIAS =
basic_format_args<basic_format_context<OutputIt, Char>>;

/**
\rst
Constructs a `~fmt::format_arg_store` object that contains references
to arguments and can be implicitly converted to `~fmt::format_args`.
If ``fmt`` is a compile-time string then `make_args_checked` checks
its validity at compile time.
\endrst
*/
template <typename... Args, typename S, typename Char = char_t<S>>
FMT_INLINE auto make_args_checked(const S& fmt,
const remove_reference_t<Args>&... args)
-> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> {
static_assert(
detail::count<(
std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
detail::check_format_string<Args...>(fmt);
return {args...};
}

template <typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_string<S>::value)>
inline void vformat_to(
Expand Down Expand Up @@ -2736,9 +2795,9 @@ namespace detail {
template <typename Char> struct udl_formatter {
basic_string_view<Char> str;

template <typename... Args>
std::basic_string<Char> operator()(Args&&... args) const {
return format(str, std::forward<Args>(args)...);
template <typename... T>
std::basic_string<Char> operator()(T&&... args) const {
return vformat(str, fmt::make_args_checked<T...>(str, args...));
}
};

Expand Down Expand Up @@ -2790,10 +2849,6 @@ constexpr auto operator"" _format(const char* s, size_t n)
-> detail::udl_formatter<char> {
return {{s, n}};
}
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}

/**
\rst
Expand Down
14 changes: 11 additions & 3 deletions include/fmt/wchar.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Formatting library for C++ - experimental wchar_t support
// Formatting library for C++ - optional wchar_t support
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
Expand All @@ -12,7 +12,7 @@

#include "format.h"

namespace fmt {
FMT_BEGIN_NAMESPACE

#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
// Workaround broken conversion on older gcc.
Expand All @@ -28,6 +28,13 @@ constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
return {args...};
}

inline namespace literals {
constexpr auto operator"" _format(const wchar_t* s, size_t n)
-> detail::udl_formatter<wchar_t> {
return {{s, n}};
}
} // namespace literals

inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
wmemory_buffer buffer;
detail::vformat_to(buffer, fmt, args);
Expand All @@ -48,6 +55,7 @@ void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
return vprint(wstring_view(fmt), make_wformat_args(args...));
}
} // namespace fmt

FMT_END_NAMESPACE

#endif // FMT_WCHAR_H_
Loading

0 comments on commit e9c1c41

Please sign in to comment.