Index: clang/lib/Format/UnwrappedLineParser.h =================================================================== --- clang/lib/Format/UnwrappedLineParser.h +++ clang/lib/Format/UnwrappedLineParser.h @@ -138,6 +138,7 @@ // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint void parseCSharpGenericTypeConstraint(); bool tryToParseLambda(); + bool tryToParseCSharpLambda(); bool tryToParseLambdaIntroducer(); bool tryToParsePropertyAccessor(); void tryToParseJSFunction(); Index: clang/lib/Format/UnwrappedLineParser.cpp =================================================================== --- clang/lib/Format/UnwrappedLineParser.cpp +++ clang/lib/Format/UnwrappedLineParser.cpp @@ -1851,6 +1851,22 @@ return true; } +bool UnwrappedLineParser::tryToParseCSharpLambda() { + // Fat arrows (=>) have tok::TokenKind tok::equal but TokenType + // TT_FatArrow. They always start an expression or a child block if + // followed by a curly brace. + nextToken(); + if (FormatTok->isNot(tok::l_brace)) + return false; + // C# may break after => if the next character is a newline. + if (Style.BraceWrapping.AfterFunction) { + // calling `addUnwrappedLine()` here causes odd parsing errors. + FormatTok->MustBreakBefore = true; + } + parseChildBlock(); + return true; +} + bool UnwrappedLineParser::parseBracedList(bool ContinueOnSemicolons, bool IsEnum, tok::TokenKind ClosingBraceKind) { @@ -1859,23 +1875,9 @@ // FIXME: Once we have an expression parser in the UnwrappedLineParser, // replace this by using parseAssignmentExpression() inside. do { - if (Style.isCSharp()) { - // Fat arrows (=>) have tok::TokenKind tok::equal but TokenType - // TT_FatArrow. They always start an expression or a child block if - // followed by a curly brace. - if (FormatTok->is(TT_FatArrow)) { - nextToken(); - if (FormatTok->is(tok::l_brace)) { - // C# may break after => if the next character is a newline. - if (Style.isCSharp() && Style.BraceWrapping.AfterFunction == true) { - // calling `addUnwrappedLine()` here causes odd parsing errors. - FormatTok->MustBreakBefore = true; - } - parseChildBlock(); - continue; - } - } - } + if (Style.isCSharp() && FormatTok->is(TT_FatArrow)) + if (tryToParseCSharpLambda()) + continue; if (Style.Language == FormatStyle::LK_JavaScript) { if (FormatTok->is(Keywords.kw_function) || FormatTok->startsSequence(Keywords.kw_async, Keywords.kw_function)) { @@ -2002,7 +2004,7 @@ break; case tok::equal: if (Style.isCSharp() && FormatTok->is(TT_FatArrow)) - parseStructuralElement(); + tryToParseCSharpLambda(); else nextToken(); break; Index: clang/unittests/Format/FormatTestCSharp.cpp =================================================================== --- clang/unittests/Format/FormatTestCSharp.cpp +++ clang/unittests/Format/FormatTestCSharp.cpp @@ -759,6 +759,128 @@ GoogleStyle); } +TEST_F(FormatTestCSharp, CSharpLambdasDontBreakFollowingCodeAlignment) { + FormatStyle GoogleStyle = getGoogleStyle(FormatStyle::LK_CSharp); + FormatStyle MicrosoftStyle = getMicrosoftStyle(FormatStyle::LK_CSharp); + + verifyFormat(R"(// +public class Sample +{ + public void Test() + { + while (true) + { + preBindEnumerators.RemoveAll(enumerator => !enumerator.MoveNext()); + CodeThatFollowsLambda(); + IsWellAligned(); + } + } +})", + MicrosoftStyle); + + verifyFormat(R"(// +public class Sample { + public void Test() { + while (true) { + preBindEnumerators.RemoveAll(enumerator => !enumerator.MoveNext()); + CodeThatFollowsLambda(); + IsWellAligned(); + } + } +})", + GoogleStyle); +} + +TEST_F(FormatTestCSharp, CSharpLambdasComplexLambdasDontBreakAlignment) { + FormatStyle GoogleStyle = getGoogleStyle(FormatStyle::LK_CSharp); + FormatStyle MicrosoftStyle = getMicrosoftStyle(FormatStyle::LK_CSharp); + + verifyFormat(R"(// +public class Test +{ + private static void ComplexLambda(BuildReport protoReport) + { + allSelectedScenes = + veryVeryLongCollectionNameThatPutsTheLineLenghtAboveTheThresholds.Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + if (allSelectedScenes.Count == 0) + { + return; + } + Functions(); + AreWell(); + Aligned(); + AfterLambdaBlock(); + } +})", + MicrosoftStyle); + + verifyFormat(R"(// +public class Test { + private static void ComplexLambda(BuildReport protoReport) { + allSelectedScenes = veryVeryLongCollectionNameThatPutsTheLineLenghtAboveTheThresholds + .Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + if (allSelectedScenes.Count == 0) { + return; + } + Functions(); + AreWell(); + Aligned(); + AfterLambdaBlock(); + } +})", + GoogleStyle); +} + +TEST_F(FormatTestCSharp, CSharpLambdasMulipleLambdasDontBreakAlignment) { + FormatStyle GoogleStyle = getGoogleStyle(FormatStyle::LK_CSharp); + FormatStyle MicrosoftStyle = getMicrosoftStyle(FormatStyle::LK_CSharp); + + verifyFormat(R"(// +public class Test +{ + private static void MultipleLambdas(BuildReport protoReport) + { + allSelectedScenes = + veryVeryLongCollectionNameThatPutsTheLineLenghtAboveTheThresholds.Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + preBindEnumerators.RemoveAll(enumerator => !enumerator.MoveNext()); + if (allSelectedScenes.Count == 0) + { + return; + } + Functions(); + AreWell(); + Aligned(); + AfterLambdaBlock(); + } +})", + MicrosoftStyle); + + verifyFormat(R"(// +public class Test { + private static void MultipleLambdas(BuildReport protoReport) { + allSelectedScenes = veryVeryLongCollectionNameThatPutsTheLineLenghtAboveTheThresholds + .Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + preBindEnumerators.RemoveAll(enumerator => !enumerator.MoveNext()); + if (allSelectedScenes.Count == 0) { + return; + } + Functions(); + AreWell(); + Aligned(); + AfterLambdaBlock(); + } +})", + GoogleStyle); +} + TEST_F(FormatTestCSharp, CSharpObjectInitializers) { FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp);