Per P1975R0 an expression like static_cast<U[]>(...) defines the type
of the expression as U[1].
Details
- Reviewers
usaxena95 shafik ayzhao aaron.ballman erichkeane cor3ntin - Group Reviewers
Restricted Project
Diff Detail
- Repository
- rG LLVM Github Monorepo
Unit Tests
Event Timeline
clang/lib/Sema/SemaType.cpp | ||
---|---|---|
8859 | For the case I'm trying to fix, T is array of unknown bound, it is already checked before calling completeExprArrayBound. Right now when you write something like int (&&arr1)[1] = static_cast<int[]>(42); Clang actually is able to realize that parenthesized initialization is made, so it actually generates CXXParenListInitExpr that has type int[1]. Here I'm just paranoidally double-checking that is the case before switching the type. Maybe I don't need this double-check at all then? |
clang/lib/Sema/SemaType.cpp | ||
---|---|---|
8859 | That makes me wonder if this is the correct place for this? Should when we generate this type when we do the CXXParenListInitExpr fixup? Either way, I think making this depend on that behavior (which would possibly be fragile), we should just do it based on the type passed ot the static_cast. Another question: is this something that needs to be done for other cast types? Does similar behavior exist for the other casts? Should it also happen with 'clobber' casts (C-style) that are effectively static? |
clang/lib/Sema/SemaType.cpp | ||
---|---|---|
8859 |
The function called completeExprArrayBound seemed like an appropriate place for me.
I'm not sure. https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1975r0.html seems to be extending only static_casts. And this was the original purpose of this bugfix. |
clang/lib/Sema/SemaType.cpp | ||
---|---|---|
8859 |
You mentioned that at a different place we set the type of the CXXParenListInitExpr to be the 1-arity array. I was saying that if there is logic THERE based on whether its an incomplete array type it would be for the same purpose, and perhaps be the correct place to do this.
Yep, thats what I was hoping to see, thanks.
I was concerned that we increasing the inconsistency by only fixing 1 of the types of casts. It might seem like 'feature creep', but it is very important that we make sure a patch is reasonably complete, so that it doesn't make the compiler less consistent. |
clang/lib/Sema/SemaType.cpp | ||
---|---|---|
8859 |
Well, it seems to be happening inside of a TryStaticImplicitCast before the CXXStaticCastExpr created. There is initialization sequence performed, and the source expression comes out of as an array of 1 element. It is not very convenient to switch the type there since there is no cast expression itself yet and the destination type is passed as a copy through several functions. Will it be ok to check and switch the type of resulting static_cast expression just before/after it is created? |
Thank you for working on this!
clang/lib/Sema/SemaCast.cpp | ||
---|---|---|
1269–1272 ↗ | (On Diff #536172) | This seems incorrect to me -- I think the CastOperation should be created with the correct result type to begin with. What you are citing is a note, but the actual wording that specifies that note is: https://eel.is/c++draft/dcl.init#general-16.5 so I think the fix approach is somewhat correct but the code lives in the wrong place, this should be closer to the initialization code I think. |
clang/test/SemaCXX/paren-list-agg-init.cpp | ||
276–285 | I'd like to see test coverage for: int (&&arr)[] = (int[])(42); int (&&arr1)[1] = (int[])(42); int (&&arrr2)[2] = (int[])(42); int (&&arr3)[3] = (int[3])(42); where we're using a C-style cast, because: http://eel.is/c++draft/expr.cast#4 |
clang/test/SemaCXX/paren-list-agg-init.cpp | ||
---|---|---|
276–285 | Thank you for the review! Just to double check, so it says:
Does that mean the c-style cast should produce the same thing? And, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1975r0.html doesn't say anything about c-style casts because it is assumed that it should be able to do anything that static_cast can do? gcc doesn't agree https://godbolt.org/z/Pfq8frdn9 . The funny thing is that the original bug report seems to be using some kind of gcc test. |
clang/test/SemaCXX/paren-list-agg-init.cpp | ||
---|---|---|
276–285 |
Correct
MSVC and ICC both agree though, so I lean towards us being on the right path in allowing those casts. @hubert.reinterpretcast -- do you have thoughts? |
This looks reasonable to me know. should we try to land that today?
Sorry i could not get to it earlier!
should we try to land that today?
I'm not sure. It causes failures in libc++ testing:
Failed Tests (3): llvm-libc++-shared.cfg.in :: std/ranges/range.access/end.pass.cpp llvm-libc++-shared.cfg.in :: std/ranges/range.access/rbegin.pass.cpp llvm-libc++-shared.cfg.in :: std/ranges/range.access/rend.pass.cpp
I haven't figured out why. Trying to compile:
#include <ranges> using RangeCREndT = decltype(std::ranges::crend); static_assert(!std::is_invocable_v<RangeCREndT, int (&)[]>);
fails with this patch only using libc++, libstdc++ is fine. I'm not sure it is my misunderstanding, bug in the patch or bug in libc++.
Thanks for the heads-up! From what I can tell, this could be a problem in the compiler, in is_invocable, in cend or in end. Unfortunately, I don't have the time right now to make a smaller reproducer and Louis isn't available. @Mordante do you have time to take a look at this? I will probably only have time to get to it by the end of the week or early next week.
I think getting a smaller reproducer would be useful indeed before talking about conformance, even though i think there is indeed something wrong there - an incomplete parameter should not be able to produce a valid call.
What do you mean with libstdc++ is fine?
The reduced tests passed with libcstd++ https://godbolt.org/z/nh9v8cedr
The change seems to break libc++.
Based on the bug and this patch I am correct that "transformation" of an array of unknown bounds to anarray of known bounds only happens with an explicit cast and not implicitly?
I have a strong suspicion that this patch changes the test to
#include <ranges> using RangeCREndT = decltype(std::ranges::crend); static_assert(!std::is_invocable_v<RangeCREndT, int (&)[1]>); // no longer an array of unknown bound so the assert fails
Can you look at the AST output of the failed example to verify my hypothesis? When you think libc++ is wrong can you then post this AST?
What do you mean with libstdc++ is fine?
What I mean is when I do (current patch is applied to clang):
source/llvm-project/build/bin/clang++ -std=c++20 t.cpp -c // compilation succeeds
But when I do (libc++ is built together with clang)
source/llvm-project/build/bin/clang++ -std=c++20 t.cpp -stdlib=libc++ -c t.cpp:4:15: error: static assertion failed due to requirement '!std::is_invocable_v<const std::ranges::__crend::__fn, int (&)[]>' 4 | static_assert(!std::is_invocable_v<RangeCREndT, int (&)[]>); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1 error generated.
Based on the bug and this patch I am correct that "transformation" of an array of unknown bounds to anarray of known bounds only happens with an explicit cast and not implicitly?
Yes, type is changed only for explicit casts.
I have a strong suspicion that this patch changes the test to
Well technically it only changes explicit casts, so no. With this patch this static assertion still fails
static_assert(std::is_same<int (&)[1], int(&)[]>::value);
What I'm seeing is that in libc++ there is a bunch of explicit static casts in ranges::__crend::__fn that endup transformed:
namespace ranges { namespace __crend { struct __fn { template <class _Tp> requires is_lvalue_reference_v<_Tp&&> [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(noexcept(ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)))) -> decltype( ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t))) { return ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)); } template <class _Tp> requires is_rvalue_reference_v<_Tp&&> [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(noexcept(ranges::rend(static_cast<const _Tp&&>(__t)))) -> decltype( ranges::rend(static_cast<const _Tp&&>(__t))) { return ranges::rend(static_cast<const _Tp&&>(__t)); } }; } // namespace __crend
Is that expected?
Please note that other implementations would do the same transformation and that is the point of this bugfix:
https://godbolt.org/z/ff8f3YEbz
I see what you mean now.
What I'm seeing is that in libc++ there is a bunch of explicit static casts in ranges::__crend::__fn that endup transformed:
namespace ranges { namespace __crend { struct __fn { template <class _Tp> requires is_lvalue_reference_v<_Tp&&> [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(noexcept(ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)))) -> decltype( ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t))) { return ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)); } template <class _Tp> requires is_rvalue_reference_v<_Tp&&> [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const noexcept(noexcept(ranges::rend(static_cast<const _Tp&&>(__t)))) -> decltype( ranges::rend(static_cast<const _Tp&&>(__t))) { return ranges::rend(static_cast<const _Tp&&>(__t)); } }; } // namespace __crendIs that expected?
I'm not very familiar with ranges but looking at the standard the static_cast is not required. I don't think the original author intended this behaviour which you implement in this patch. (To be honest I'm a bit surprised by this change in the language. I wonder how many other (library) developers it will catch off-guard.)
I agree this is a bug in libc++ and we should fix it. As mentioned I'm not too familiar with ranges. Maybe @var-const has time, otherwise
we need to wait for @philnik.
I agree this is a bug in libc++ and we should fix it. As mentioned I'm not too familiar with ranges.
Thanks! I submitted https://github.com/llvm/llvm-project/issues/64250 so we don't forget about it.
clang-format not found in user’s local PATH; not linting file.