Skip to content
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

Fix GCC 15 build errors with FMT_MODULE=ON #4347

Merged
merged 2 commits into from
Feb 26, 2025
Merged

Conversation

tkhyn
Copy link
Contributor

@tkhyn tkhyn commented Feb 13, 2025

Hi again,

I'm not sure how far you want to support GCC 15 before it's released, but here are a few fixes in case fmt needs to be built as a module with it. I doesn't look like merging the changes now would have any adverse effect.

Here are the steps to reproduce, with cmake 3.31.5 and GCC 15.0.1 20250205 (I couldn't build today's master for some reason) installed in /opt/gcc-dev:

mkdir build-gcc-15 && cd build-gcc-15
export CC=/opt/gcc-dev/bin/gcc
export CXX=/opt/gcc-dev/bin/g++
cmake .. -DFMT_MODULE=ON
cmake --build --target fmt
before 'Fix partial specialization in unbraced export-declaration':
In file included from /mnt/d/dev/libs/tools/fmt/src/fmt.cc:128:
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:117:33: error: declaration of partial specialization in unbraced export-declaration
  117 | template <typename Char> struct formatter<std::filesystem::path, Char> {
      |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:117:33: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:187:8: error: declaration of partial specialization in unbraced export-declaration
  187 | struct formatter<std::bitset<N>, Char>
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:187:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:214:8: error: declaration of partial specialization in unbraced export-declaration
  214 | struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:214:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:221:8: error: declaration of partial specialization in unbraced export-declaration
  221 | struct formatter<std::optional<T>, Char,
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  222 |                  std::enable_if_t<is_formattable<T, Char>::value>> {
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:221:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:315:1: error: explicit specializations are not permitted here
  315 | template <> struct formatter<std::source_location> {
      | ^~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:315:1: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:371:33: error: declaration of partial specialization in unbraced export-declaration
  371 | template <typename Char> struct formatter<std::monostate, Char> {
      |                                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:371:33: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:385:8: error: declaration of partial specialization in unbraced export-declaration
  385 | struct formatter<
      |        ^~~~~~~~~~
  386 |     Variant, Char,
      |     ~~~~~~~~~~~~~~
  387 |     std::enable_if_t<std::conjunction_v<
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  388 |         is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:385:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:418:1: error: explicit specializations are not permitted here
  418 | template <> struct formatter<std::error_code> {
      | ^~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:418:1: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:525:8: error: declaration of partial specialization in unbraced export-declaration
  525 | struct formatter<std::type_info, Char  // DEPRECATED! Mixing code unit types.
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  526 |                  > {
      |                  ~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:525:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:542:8: error: declaration of partial specialization in unbraced export-declaration
  542 | struct formatter<
      |        ^~~~~~~~~~
  543 |     T, Char,  // DEPRECATED! Mixing code unit types.
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  544 |     typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:542:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:608:8: error: declaration of partial specialization in unbraced export-declaration
  608 | struct formatter<BitRef, Char,
      |        ^~~~~~~~~~~~~~~~~~~~~~~
  609 |                  enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:608:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:628:8: error: declaration of partial specialization in unbraced export-declaration
  628 | struct formatter<std::atomic<T>, Char,
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  629 |                  enable_if_t<is_formattable<T, Char>::value>>
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:628:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:641:8: error: declaration of partial specialization in unbraced export-declaration
  641 | struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:641:8: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:651:45: error: declaration of partial specialization in unbraced export-declaration
  651 | template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
      |                                             ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:651:45: note: a specialization is always exported alongside its primary template
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:715:8: error: declaration of partial specialization in unbraced export-declaration
  715 | struct formatter<std::reference_wrapper<T>, Char,
      |        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  716 |                  enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
      |                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/std.h:715:8: note: a specialization is always exported alongside its primary template

I couldn't find details or a reference in the C++ standard to fully understand the whys but my understanding is you can't directly export a partial template specialization. I think I got all the export blocks right but it would definitely warrant another check

before 'Fix TU-local entity exposition errors':
In file included from /mnt/d/dev/libs/tools/fmt/src/fmt.cc:148:
/mnt/d/dev/libs/tools/fmt/src/os.cc:74:7: error: ‘using {anonymous}::rwresult = ssize_t’ exposes TU-local entity ‘{anonymous}’
   74 | using rwresult = ssize_t;
      |       ^~~~~~~~
/mnt/d/dev/libs/tools/fmt/src/os.cc:62:1: note: ‘{anonymous}’ declared with internal linkage
   62 | namespace {
      | ^~~~~~~~~
In file included from /mnt/d/dev/libs/tools/fmt/src/format.cc:8,
                 from /mnt/d/dev/libs/tools/fmt/src/fmt.cc:145:
/mnt/d/dev/libs/tools/fmt/include/fmt/format-inl.h:218:3: error: ‘fmt::v11::detail::dragonbox::div_small_pow10_infos’ exposes TU-local entity ‘struct fmt::v11::detail::dragonbox::<unnamed>’
  218 | } div_small_pow10_infos[] = {{10, 16}, {100, 16}};
      |   ^~~~~~~~~~~~~~~~~~~~~
/mnt/d/dev/libs/tools/fmt/include/fmt/format-inl.h:215:38: note: ‘fmt::v11::detail::dragonbox::<unnamed struct>’ has no name and is not defined within a class, function, or initializer
  215 | FMT_INLINE_VARIABLE constexpr struct {
      |                                      ^

These 2 errors are related to the implementation of P1815, which aims at "forbidding exposure of internal-linkage constructs to other translation units" and may make its way into Clang at some stage.

Better suggestions welcome for the name of the previously unnamed struct instantiated in div_small_pow10_infos in format-inl.h. As it's already in details::dragonbox its actual name doesn't matter much.

I have checked it didn't break GCC 14, Clang 21 nor MSVC builds, whether FMT_MODULE is on or not.

@tkhyn tkhyn force-pushed the master branch 3 times, most recently from 87f88bf to 9381bba Compare February 13, 2025 08:53
@tkhyn
Copy link
Contributor Author

tkhyn commented Feb 13, 2025

Tried to make the linter happy, but not agreeing with it in std.h L278

Copy link
Contributor

@vitaut vitaut left a 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

FMT_BEGIN_EXPORT
template <typename Char> struct formatter<std::filesystem::path, Char> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering that clang and MSVC accept export on partial specializations, I don't think we should make this and similar changes until there is clarity which one is correct.

Copy link
Contributor

@vitaut vitaut Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this is the relevant paper that made export of partial specializations ill-formed: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2615r0.html.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the correct fix is to remove FMT_EXPORT without wrapping in FMT_BEGIN_EXPORT / FMT_END_EXPORT.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I'll take your word for it as I'm not sure I'm qualified enough to be 100% certain it's not going to break anything. I didn't get anywhere trying to build the test-module.cc and only have a very minimal test project to test these changes with.

src/os.cc Outdated
Comment on lines 263 to 268
// Return type of read and write functions.
# ifdef _WIN32
# define rwresult int
# elif FMT_USE_FCNTL
# define rwresult ssize_t
# endif
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a way to fix this error:

In file included from /mnt/d/dev/libs/tools/fmt/src/fmt.cc:148:
/mnt/d/dev/libs/tools/fmt/src/os.cc:74:7: error: ‘using {anonymous}::rwresult = ssize_t’ exposes TU-local entity ‘{anonymous}’
   74 | using rwresult = ssize_t;
      |       ^~~~~~~~
/mnt/d/dev/libs/tools/fmt/src/os.cc:62:1: note: ‘{anonymous}’ declared with internal linkage
   62 | namespace {
      | ^~~~~~~~~

When using using, rwresult becomes a TU-local type and has internal linkage because it's in anonymous namespace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typedefs are just aliases and shouldn't have linkage of their own. And even if they had we wouldn't want to replace them with macros. Please revert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that typedefs inherit the linkage of the type they refer to. This means that the name rwresult has linkage because - in that case - it refers to ssize_t, which is a fundamental type and therefore has linkage itself (see basic.link).

I initially thought of 2 ways to work around this (maybe there are other ways):

  • take the using rwresult = <type> out of the anonymous namespace, but then this exposes rwresult, which was hidden previously so I elected not to do that
  • use a macro which can be #undefed hence not exposing the rwresult name

I'm happy to take rwresult out of the anonymous namespace if you don't mind exposing rwresult, unless there is a more elegant solution.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure gcc is correct here - it even seems to be inconsistent not just with other compilers but with itself since there are other TU-local symbols here like convert_rwcount. I suggest reverting this change and possibly opening an issue in gcc to clarify why it complains for some symbols and not the other.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, indeed, it's weird that convert_rwcount doesn't raise anything. Enquiry sent on gcc-help, I'll keep you posted.

@vitaut
Copy link
Contributor

vitaut commented Feb 21, 2025

@tkhyn do you plan to update the PR, addressing the review comments, or shall we close it for now?

@tkhyn
Copy link
Contributor Author

tkhyn commented Feb 22, 2025

Yes I did intend to follow up, but didn't have the time to look into the rwresult thing.

While filing a GCC issue, I found this one. Which means you were right and GCC was incorrect in reporting an error with rwresult. There is a patch coming up.

Push coming soon to revert the macro hack and remove the FMT_EXPORT statements.

This fixes 'declaration of partial specialization in unbraced export-declaration' errors in GCC 15
@vitaut
Copy link
Contributor

vitaut commented Feb 22, 2025

LGTM but could you check that removing FMT_EXPORT on instantiations doesn't break clang? We don't have CI coverage for modules yet.

@tkhyn
Copy link
Contributor Author

tkhyn commented Feb 22, 2025

Clang + libstdc++ builds fine. Clang + libc++ builds were broken and still are (# include <atomic> is missing in fmt.cc).

However as mentioned earlier I didn't manage to build test-module.cc (notwithstanding the CMake config that really doesn't want you to build any test target when FMT_MODULE is ON, there are many compiler errors coming from module-test.cc or gmock-gtest-all.cc that I didn't have the courage nor time to look into). I do not think the use I make of fmt in my small project is extensive enough to validate it doesn't break anything. That's the reason why I had reservations about removing FMT_EXPORT everywhere in std.h.

@vitaut vitaut merged commit 577fd3b into fmtlib:master Feb 26, 2025
45 checks passed
@vitaut
Copy link
Contributor

vitaut commented Feb 26, 2025

Merged, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants