-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Report error on duplicate named arg names #4367
Conversation
Wanted to mention that this does not handle the case of using |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
include/fmt/base.h
Outdated
for (auto i = 0; i < named_arg_index; ++i) { | ||
if (basic_string_view<Char>(named_args[i].name) == | ||
basic_string_view<Char>(arg.name)) { | ||
report_error("duplicate named args found"); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move it to a separate function in detail
and reuse here and below.
include/fmt/base.h
Outdated
@@ -1071,6 +1071,12 @@ void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) { | |||
template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> | |||
void init_named_arg(named_arg_info<Char>* named_args, int& arg_index, | |||
int& named_arg_index, const T& arg) { | |||
for (auto i = 0; i < named_arg_index; ++i) { | |||
if (basic_string_view<Char>(named_args[i].name) == | |||
basic_string_view<Char>(arg.name)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to move this out of the loop to avoid recomputing length.
expect_compile(format-string-duplicate-name-error " | ||
#if defined(FMT_HAS_CONSTEVAL) && FMT_USE_NONTYPE_TEMPLATE_ARGS | ||
using namespace fmt::literals; | ||
fmt::print(\"{bar}\", \"bar\"_a=42, \"bar\"_a=43); | ||
#else | ||
#error | ||
#endif | ||
" ERROR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests are expensive and we don't need to use them here, please remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the situations where you'd want to add these? I figured it was important to verify that compile fails in this situation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is basically an integration test that checks that the internal checking machinery translates into actual compiler errors. It shouldn't be used for unit testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess I'm a little confused because that's what this is doing. I wrote a unit test for the runtime exception version. I figured since there's also a compile time failure, a test would be required to make sure it functions, especially since the two routes are different in the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could unit test that exception is thrown but I think it's fine as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did add a test to verify dynamic named args cause exceptions at runtime. In order to compile the above lines and verify it throws would require I only enable the test when we're not using non-type template parameters. In that situation, it just devolves to the dynamic named args and we don't test the static, compile-time check machinery, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Compile-time check machinery just translates exceptions to compile-time errors and it's (roughly) the same for all errors so it doesn't need to be tested multiple times. It's sufficient to test the exception path.
Just add a comment in #4124 since it's part of general named argument support in format string compilation. |
I'm not sure why the refactoring made GCC13 unhappy, but it's complaining about possibly uninitialized values being referenced. I can initialize those values in the field and make GCC13 happy, but then GCC10 with C++11 breaks because of some initializer list-style initializations. I'm going to try and add a constructor to make everybody happy and you can let me know if there is a better way. For reference, this was the warning that was treated as an error:
|
include/fmt/base.h
Outdated
FMT_CONSTEXPR named_arg_info() : name(nullptr), id(0) {} | ||
FMT_CONSTEXPR named_arg_info(const Char* a_name, const int an_id) | ||
: name(a_name), id(an_id) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initialization is done in init*named_arg
, we shouldn't duplicate it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to try your idea of only calling if the index is greater than zero, but if that doesn't work, I need the default construct and if I have the default construct, GCC10 will need the other one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both functions have been removed. The if check appeased GCC13, it seems.
include/fmt/base.h
Outdated
@@ -1084,6 +1101,7 @@ template <typename T, typename Char, | |||
FMT_ENABLE_IF(is_static_named_arg<T>::value)> | |||
FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args, | |||
int& arg_index, int& named_arg_index) { | |||
check_for_duplicate(named_args, named_arg_index, T::name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if calling check_for_duplicate
only if named_arg_index
is nonzero fixes the uninitialized warning.
include/fmt/base.h
Outdated
const Char* const arg_name) { | ||
const basic_string_view<Char> arg_name_view(arg_name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can simplify this by passing arg_name as a string_view.
include/fmt/base.h
Outdated
const Char* name; | ||
int id; | ||
}; | ||
|
||
template <typename Char> | ||
FMT_CONSTEXPR void check_for_duplicate( | ||
const named_arg_info<Char>* const named_args, const int named_arg_index, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's not overuse const - it just clutters the code without bringing much benefit when the scope is small.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just want my immutable by default like rust 😄 . This is also an area I disagree with the CPP core guidelines. I'll begrudgingly update this, though.
…eturn early based on named_arg_index
Merged, thanks. One drawback of this is quadratic complexity but this can be addressed later. |
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args, | ||
int named_arg_index, | ||
basic_string_view<Char> arg_name) { | ||
if (named_arg_index <= 0) return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As it turned out the actual change that worked around the gcc bug was removing const in named_args
, not adding the check =)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, that's kind of funny. Which const was it, cause I had a lot 😄 Makes me curious why the const additions made it seem like it might not be initialized to GCC. Maybe it just generated some weird assumptions in the compiler checks as part of the inlining process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const named_arg_info<Char>*
, looks like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100417
Compile error will happen if static named args are used and enabled. Runtime exception will be thrown in other cases.
#4282