diff --git a/clang/lib/Format/ContinuationIndenter.h b/clang/lib/Format/ContinuationIndenter.h --- a/clang/lib/Format/ContinuationIndenter.h +++ b/clang/lib/Format/ContinuationIndenter.h @@ -208,7 +208,8 @@ LastOperatorWrapped(true), ContainsLineBreak(false), ContainsUnwrappedBuilder(false), AlignColons(true), ObjCSelectorNameFound(false), HasMultipleNestedBlocks(false), - NestedBlockInlined(false), IsInsideObjCArrayLiteral(false) {} + NestedBlockInlined(false), IsInsideObjCArrayLiteral(false), + IsCSharpGenericTypeConstraint(false) {} /// \brief The token opening this parenthesis level, or nullptr if this level /// is opened by fake parenthesis. @@ -329,6 +330,8 @@ /// array literal. bool IsInsideObjCArrayLiteral : 1; + bool IsCSharpGenericTypeConstraint : 1; + bool operator<(const ParenState &Other) const { if (Indent != Other.Indent) return Indent < Other.Indent; @@ -366,6 +369,8 @@ return ContainsUnwrappedBuilder; if (NestedBlockInlined != Other.NestedBlockInlined) return NestedBlockInlined; + if (IsCSharpGenericTypeConstraint != Other.IsCSharpGenericTypeConstraint) + return IsCSharpGenericTypeConstraint; return false; } }; diff --git a/clang/lib/Format/ContinuationIndenter.cpp b/clang/lib/Format/ContinuationIndenter.cpp --- a/clang/lib/Format/ContinuationIndenter.cpp +++ b/clang/lib/Format/ContinuationIndenter.cpp @@ -634,6 +634,7 @@ State.Stack.back().NoLineBreak = true; if (Style.AlignAfterOpenBracket != FormatStyle::BAS_DontAlign && + !State.Stack.back().IsCSharpGenericTypeConstraint && Previous.opensScope() && Previous.isNot(TT_ObjCMethodExpr) && (Current.isNot(TT_LineComment) || Previous.BlockKind == BK_BracedInit)) State.Stack.back().Indent = State.Column + Spaces; @@ -715,6 +716,8 @@ } else if (Previous.is(TT_InheritanceColon)) { State.Stack.back().Indent = State.Column; State.Stack.back().LastSpace = State.Column; + } else if (Current.is(TT_CSharpGenericTypeConstraintColon)) { + State.Stack.back().ColonPos = State.Column; } else if (Previous.opensScope()) { // If a function has a trailing call, indent all parameters from the // opening parenthesis. This avoids confusing indents like: @@ -924,7 +927,13 @@ unsigned ContinuationIndenter::getNewLineColumn(const LineState &State) { if (!State.NextToken || !State.NextToken->Previous) return 0; + FormatToken &Current = *State.NextToken; + + if (State.Stack.back().IsCSharpGenericTypeConstraint && + Current.isNot(TT_CSharpGenericTypeConstraint)) + return State.Stack.back().ColonPos + 2; + const FormatToken &Previous = *Current.Previous; // If we are continuing an expression, we want to use the continuation indent. unsigned ContinuationIndent = @@ -1106,9 +1115,11 @@ assert(State.Stack.size()); const FormatToken &Current = *State.NextToken; + if (Current.is(TT_CSharpGenericTypeConstraint)) + State.Stack.back().IsCSharpGenericTypeConstraint = true; if (Current.isOneOf(tok::comma, TT_BinaryOperator)) State.Stack.back().NoLineBreakInOperand = false; - if (Current.is(TT_InheritanceColon)) + if (Current.isOneOf(TT_InheritanceColon, TT_CSharpGenericTypeConstraintColon)) State.Stack.back().AvoidBinPacking = true; if (Current.is(tok::lessless) && Current.isNot(TT_OverloadedOperator)) { if (State.Stack.back().FirstLessLess == 0) @@ -1329,6 +1340,11 @@ if (!Current.opensScope()) return; + // Don't allow '<' or '(' in C# generic type constraints to start new scopes. + if (Current.isOneOf(tok::less, tok::l_paren) && + State.Stack.back().IsCSharpGenericTypeConstraint) + return; + if (Current.MatchingParen && Current.BlockKind == BK_Block) { moveStateToNewBlock(State); return; @@ -1393,6 +1409,7 @@ (State.Line->Type == LT_ObjCDecl && ObjCBinPackProtocolList); AvoidBinPacking = + (State.Stack.back().IsCSharpGenericTypeConstraint) || (Style.Language == FormatStyle::LK_JavaScript && EndsInComma) || (State.Line->MustBeDeclaration && !BinPackDeclaration) || (!State.Line->MustBeDeclaration && !Style.BinPackArguments) || diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -3620,6 +3620,9 @@ if (Left.isOneOf(TT_CSharpNamedArgumentColon, TT_AttributeColon) || Right.isOneOf(TT_CSharpNamedArgumentColon, TT_AttributeColon)) return false; + // Only break after commas for generic type constraints. + if (Line.First->is(TT_CSharpGenericTypeConstraint)) + return Left.is(TT_CSharpGenericTypeConstraintComma); } else if (Style.Language == FormatStyle::LK_Java) { if (Left.isOneOf(Keywords.kw_throws, Keywords.kw_extends, Keywords.kw_implements)) diff --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp --- a/clang/unittests/Format/FormatTestCSharp.cpp +++ b/clang/unittests/Format/FormatTestCSharp.cpp @@ -687,6 +687,14 @@ where T : IMyInterface { doThing(); } })", Style); + + verifyFormat(R"(// +class ItemFactory + where T : new(), + IAnInterface, + IAnotherInterface, + IAnotherInterfaceStill {})", + Style); } } // namespace format