This is an archive of the discontinued LLVM Phabricator instance.

[clang-format] Annotate lambdas with requires clauses.
ClosedPublic

Authored by rymiel on Mar 8 2023, 7:31 PM.

Details

Summary

The C++ grammar allows lambdas to have a *requires-clause* in two
places, either directly after the *template-parameter-list*, such as:

[] <typename T> requires foo<T> (T t) { ... };

Or, at the end of the *lambda-declarator* (before the lambda's body):

[] <typename T> (T t) requires foo<T> { ... };

Previously, these cases weren't handled at all, resulting in weird
results.

Note that this commit only handles token annotation, so the actual
formatting still ends up suboptimal. This is mostly because I do not yet
know how to approach making the requires clause formatting of lambdas
match the formatting for functions.

Fixes https://github.com/llvm/llvm-project/issues/61269

Diff Detail

Event Timeline

rymiel created this revision.Mar 8 2023, 7:31 PM
Herald added a project: Restricted Project. · View Herald TranscriptMar 8 2023, 7:31 PM
rymiel requested review of this revision.Mar 8 2023, 7:31 PM
Herald added a project: Restricted Project. · View Herald TranscriptMar 8 2023, 7:31 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
HazardyKnusperkeks added inline comments.
clang/lib/Format/UnwrappedLineParser.cpp
3411

don't need else after break.
In fact I would negate the condition and return, and keep the remainder out of any if or else.

This revision is now accepted and ready to land.Mar 9 2023, 4:32 AM
rymiel updated this revision to Diff 503753.Mar 9 2023, 6:36 AM

Improve code flow in parseConstraintExpression

rymiel marked an inline comment as done.Mar 9 2023, 6:37 AM
rymiel added inline comments.
clang/lib/Format/UnwrappedLineParser.cpp
3411

Thank you! I clearly wasn't thinking clearly there

rymiel marked an inline comment as done.Mar 9 2023, 6:37 AM

Would this patch make the end result look worse without "making the requires clause formatting of lambdas
match the formatting for functions"? If yes, then we should not land it just yet.

Would this patch make the end result look worse without "making the requires clause formatting of lambdas
match the formatting for functions"? If yes, then we should not land it just yet.

Depends on your definition of "worse" :)
I outlined this on the github issue (https://github.com/llvm/llvm-project/issues/61269), essentially:

The result for the example provided on the github issue something like this:

[&]<typename Callable>(Callable&& callable)
  requires std::is_invocable_v<Callable>
{ static_cast<void>(callable); };

Unlike function declarations, lambdas with a requires clause still have the body on one line. I don't yet know how to go about changing this, and if I should at all. This of course isn't an issue if the lambda body has multiple lines.

There is also the case of requires clauses before the parameters, which results in this:

[&]<typename Callable>
  requires std::is_invocable_v<Callable>
(Callable &&callable) { static_cast<void>(callable); };

This poses a whole new set of problems (like, starting a line with an opening paren is pretty weird!)
But, this sort of syntax seems fairly obscure: I only found out about it reading the C++ grammar, and the clang frontend itself doesn't actually parse it at all in some cases (I filed https://github.com/llvm/llvm-project/issues/61278 for this)

Would this patch make the end result look worse without "making the requires clause formatting of lambdas
match the formatting for functions"? If yes, then we should not land it just yet.

Depends on your definition of "worse" :)
I outlined this on the github issue (https://github.com/llvm/llvm-project/issues/61269), essentially:

The result for the example provided on the github issue something like this:

[&]<typename Callable>(Callable&& callable)
  requires std::is_invocable_v<Callable>
{ static_cast<void>(callable); };

Unlike function declarations, lambdas with a requires clause still have the body on one line. I don't yet know how to go about changing this, and if I should at all. This of course isn't an issue if the lambda body has multiple lines.

I'm more concerned with the above case. If the body of the lambda (including the enclosing braces) should not be merged into a single line, then I would say the token-annotation-only fix might trigger regressions. Otherwise, we can safely land this patch now.

Could you please clarify what you mean by "regressions" here? Isn't the behaviour of this syntax broken to begin with? It doesn't change anything about lambdas without requires-clauses

Could you please clarify what you mean by "regressions" here? Isn't the behaviour of this syntax broken to begin with? It doesn't change anything about lambdas without requires-clauses

Let's use you example:

$ cat test.cpp
#include <type_traits>

template <typename T>
struct foo {
    static const bool value = true;
};

template <typename T>
inline constexpr bool foo_v = foo<T>::value;

template <typename T>
concept foo_c = foo_v<T>;

int main() {
  [&]<typename Callable>
    requires foo<Callable>::value
  (Callable&& callable)
  {
    static_cast<void>(callable);
  };

  [&]<typename Callable>
    requires foo_c<Callable>
  (Callable&& callable)
  {
    static_cast<void>(callable);
  };

  [&]<typename Callable>
    requires foo_v<Callable>
  (Callable&& callable)
  {
    static_cast<void>(callable);
  };
}
$ clang-format -version
clang-format version 17.0.0 (https://github.com/llvm/llvm-project f6e7a5c29221f445e4cbddc32667a1e12a1446db)
$ clang-format test.cpp
#include <type_traits>

template <typename T> struct foo {
  static const bool value = true;
};

template <typename T> inline constexpr bool foo_v = foo<T>::value;

template <typename T>
concept foo_c = foo_v<T>;

int main() {
  [&]<typename Callable>
    requires foo<Callable>::value(Callable && callable)
  {
    static_cast<void>(callable);
  };

  [&]<typename Callable>
    requires foo_c<Callable>(Callable && callable)
  {
    static_cast<void>(callable);
  };

  [&]<typename Callable>
    requires foo_v<Callable>(Callable && callable)
  {
    static_cast<void>(callable);
  };
}

If this patch would merge the lambda bodies into single lines, would that be considered a possible regression? My guess is that it would be unless the lambda bodies should be merged in the first place, in which case the patch would also fix a formatting bug in addition to the annotation bug.

starting a line with an opening paren is pretty weird!)

I do not think this is weird. On the contrary, this is more readable to me and separates the requires clause from the parameters list. For example this one looks so much better:

// trunk.
template <typename T>
void func() {
  auto x = []<typename U>
    requires Foo<T> && Foo<T>(T t, U u) requires BarBar<U> && BarBar<U> ||
                 BarBar<U>
  {
    return u + t;
  };
}
// patch.
template <typename T> void func() {
  auto x = []<typename U>
    requires Foo<T> && Foo<T>
  (T t, U u)
    requires BarBar<U> && BarBar<U> || BarBar<U>
  { return u + t; };
}

Discussion on wrapping the lambda body with single statement. FWIW: I do not think this is a regression and we are fixing things as seen in my first example.

Another point:
While testing this patch, the following still fails to recognise. Might be something special with true.

auto y = [&]<typename Callable>
    requires true(Callable && callable)
  { static_cast<void>(callable); };
clang/unittests/Format/TokenAnnotatorTest.cpp
1368

This is valid and is accepted by the compilers https://godbolt.org/z/EPbrWbrsv

rymiel updated this revision to Diff 507439.Mar 22 2023, 11:16 AM

Fix true edge case

rymiel updated this revision to Diff 507440.Mar 22 2023, 11:17 AM

Remove unnecessary musing

Another point:
While testing this patch, the following still fails to recognise. Might be something special with true.

auto y = [&]<typename Callable>
    requires true(Callable && callable)
  { static_cast<void>(callable); };

Thank you for finding that edge case, I've fixed it and added a test for it

rymiel marked an inline comment as done.Mar 22 2023, 11:18 AM
rymiel added inline comments.
clang/unittests/Format/TokenAnnotatorTest.cpp
1368

Thank you!

rymiel marked an inline comment as done.Mar 22 2023, 11:18 AM

So, it took me a while but I finally found where the logic is that makes the lambda braces stay on one line, but, now I'm not so sure if I should change it:

The thing I wanted to avoid was cases like

[&]<typename T>(T&& t)
requires T
{ t; };

Simply because "those braces don't really look nice", but it turns out there's a sort of "prior precedent" for this:

For example, FormatTest.cpp line 22046, LambdaWithLineComments:

verifyFormat("auto k = []() // X\n"
             "{ return; }",
             LLVMWithBeforeLambdaBody);
verifyFormat(
    "auto k = []() // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n"
    "{ return; }",
    LLVMWithBeforeLambdaBody);

(this is with combination of BraceWrapping.BeforeLambdaBody = true and AllowShortLambdaOnASingleLine)
(+ a change in C# which I would like to avoid touching since I exclusively do C++)

So, I guess I have the following choices:

  1. do nothing! (allow those braces to stay on one line)
  2. change the behavior of those existing tests, with whatever impact that has
  3. make a very specific exception for just requires-clauses

(+ maybe other compromise options, i.e. making it configurable or something)

@owenpan do you perhaps have insight for this?

I think this is okay. A followup could handle single line lambdas. But I have a personal struggle with them, especially the name AllowShortLambdasOnASingleLine no it is not allow if turned on it is force. And in combination with ColumnLimit 0 there seem to be some mismatches (never bothered enough to investigate, or if it is just a feeling).

The question would be if something with a requires clause should be merges into one line, then I think it should put everything in one line. But what if that now is in conflict with RequiresClausePosition?

owenpan accepted this revision.Mar 23 2023, 2:12 PM

@rymiel I'm deferring to you and @HazardyKnusperkeks, so doing nothing for now is fine.

This revision was landed with ongoing or failed builds.Mar 25 2023, 6:42 PM
This revision was automatically updated to reflect the committed changes.