diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h index 1b885b518f0d0..10a5f0e96f96f 100644 --- a/clang/lib/Format/FormatToken.h +++ b/clang/lib/Format/FormatToken.h @@ -108,6 +108,9 @@ namespace format { TYPE(CSharpNullCoalescing) \ TYPE(CSharpNullConditional) \ TYPE(CSharpNullConditionalLSquare) \ + TYPE(CSharpGenericTypeConstraint) \ + TYPE(CSharpGenericTypeConstraintColon) \ + TYPE(CSharpGenericTypeConstraintComma) \ TYPE(Unknown) enum TokenType { @@ -779,6 +782,7 @@ struct AdditionalKeywords { kw_unsafe = &IdentTable.get("unsafe"); kw_ushort = &IdentTable.get("ushort"); kw_when = &IdentTable.get("when"); + kw_where = &IdentTable.get("where"); // Keep this at the end of the constructor to make sure everything here // is @@ -796,6 +800,7 @@ struct AdditionalKeywords { kw_is, kw_lock, kw_null, kw_object, kw_out, kw_override, kw_params, kw_readonly, kw_ref, kw_string, kw_stackalloc, kw_sbyte, kw_sealed, kw_uint, kw_ulong, kw_unchecked, kw_unsafe, kw_ushort, kw_when, + kw_where, // Keywords from the JavaScript section. kw_as, kw_async, kw_await, kw_declare, kw_finally, kw_from, kw_function, kw_get, kw_import, kw_is, kw_let, kw_module, kw_readonly, @@ -900,6 +905,7 @@ struct AdditionalKeywords { IdentifierInfo *kw_unsafe; IdentifierInfo *kw_ushort; IdentifierInfo *kw_when; + IdentifierInfo *kw_where; /// Returns \c true if \p Tok is a true JavaScript identifier, returns /// \c false if it is a keyword or a pseudo keyword. diff --git a/clang/lib/Format/TokenAnnotator.cpp b/clang/lib/Format/TokenAnnotator.cpp index d546a9f7c6067..7193c8e6de448 100644 --- a/clang/lib/Format/TokenAnnotator.cpp +++ b/clang/lib/Format/TokenAnnotator.cpp @@ -1047,6 +1047,11 @@ class AnnotatingParser { Keywords.kw___has_include_next)) { parseHasInclude(); } + if (Tok->is(Keywords.kw_where) && Tok->Next && + Tok->Next->isNot(tok::l_paren)) { + Tok->Type = TT_CSharpGenericTypeConstraint; + parseCSharpGenericTypeConstraint(); + } break; default: break; @@ -1054,6 +1059,30 @@ class AnnotatingParser { return true; } + void parseCSharpGenericTypeConstraint() { + while (CurrentToken) { + if (CurrentToken->is(tok::less)) { + // parseAngle is too greedy and will consume the whole line. + CurrentToken->Type = TT_TemplateOpener; + next(); + } else if (CurrentToken->is(tok::greater)) { + CurrentToken->Type = TT_TemplateCloser; + next(); + } else if (CurrentToken->is(tok::comma)) { + CurrentToken->Type = TT_CSharpGenericTypeConstraintComma; + next(); + } else if (CurrentToken->is(Keywords.kw_where)) { + CurrentToken->Type = TT_CSharpGenericTypeConstraint; + next(); + } else if (CurrentToken->is(tok::colon)) { + CurrentToken->Type = TT_CSharpGenericTypeConstraintColon; + next(); + } else { + next(); + } + } + } + void parseIncludeDirective() { if (CurrentToken && CurrentToken->is(tok::less)) { next(); @@ -3299,6 +3328,8 @@ bool TokenAnnotator::mustBreakBefore(const AnnotatedLine &Line, if (Right.is(TT_CSharpNamedArgumentColon) || Left.is(TT_CSharpNamedArgumentColon)) return false; + if (Right.is(TT_CSharpGenericTypeConstraint)) + return true; } else if (Style.Language == FormatStyle::LK_JavaScript) { // FIXME: This might apply to other languages and token kinds. if (Right.is(tok::string_literal) && Left.is(tok::plus) && Left.Previous && diff --git a/clang/lib/Format/UnwrappedLineFormatter.cpp b/clang/lib/Format/UnwrappedLineFormatter.cpp index 84ccbec2150d1..a81d480c8e64f 100644 --- a/clang/lib/Format/UnwrappedLineFormatter.cpp +++ b/clang/lib/Format/UnwrappedLineFormatter.cpp @@ -64,6 +64,8 @@ class LevelIndentTracker { } if (static_cast(Indent) + Offset >= 0) Indent += Offset; + if (Line.First->is(TT_CSharpGenericTypeConstraint)) + Indent = Line.Level * Style.IndentWidth + Style.ContinuationIndentWidth; } /// Update the indent state given that \p Line indent should be diff --git a/clang/lib/Format/UnwrappedLineParser.cpp b/clang/lib/Format/UnwrappedLineParser.cpp index 00447ebdf5a92..e2a6389cb26df 100644 --- a/clang/lib/Format/UnwrappedLineParser.cpp +++ b/clang/lib/Format/UnwrappedLineParser.cpp @@ -323,6 +323,24 @@ void UnwrappedLineParser::parseFile() { addUnwrappedLine(); } +void UnwrappedLineParser::parseCSharpGenericTypeConstraint() { + do { + switch (FormatTok->Tok.getKind()) { + case tok::l_brace: + return; + default: + if (FormatTok->is(Keywords.kw_where)) { + addUnwrappedLine(); + nextToken(); + parseCSharpGenericTypeConstraint(); + break; + } + nextToken(); + break; + } + } while (!eof()); +} + void UnwrappedLineParser::parseCSharpAttribute() { int UnpairedSquareBrackets = 1; do { @@ -1344,6 +1362,12 @@ void UnwrappedLineParser::parseStructuralElement() { parseTryCatch(); return; case tok::identifier: { + if (Style.isCSharp() && FormatTok->is(Keywords.kw_where) && + Line->MustBeDeclaration) { + addUnwrappedLine(); + parseCSharpGenericTypeConstraint(); + break; + } if (FormatTok->is(TT_MacroBlockEnd)) { addUnwrappedLine(); return; diff --git a/clang/lib/Format/UnwrappedLineParser.h b/clang/lib/Format/UnwrappedLineParser.h index e184cf5354fd1..42b8b51a37cc0 100644 --- a/clang/lib/Format/UnwrappedLineParser.h +++ b/clang/lib/Format/UnwrappedLineParser.h @@ -126,6 +126,10 @@ class UnwrappedLineParser { void parseJavaScriptEs6ImportExport(); void parseStatementMacro(); void parseCSharpAttribute(); + // Parse a C# generic type constraint: `where T : IComparable`. + // See: + // https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/where-generic-type-constraint + void parseCSharpGenericTypeConstraint(); bool tryToParseLambda(); bool tryToParseLambdaIntroducer(); void tryToParseJSFunction(); diff --git a/clang/unittests/Format/FormatTestCSharp.cpp b/clang/unittests/Format/FormatTestCSharp.cpp index 03ebe337e76c8..9746f6e15322b 100644 --- a/clang/unittests/Format/FormatTestCSharp.cpp +++ b/clang/unittests/Format/FormatTestCSharp.cpp @@ -628,7 +628,6 @@ TEST_F(FormatTestCSharp, CSharpSpaces) { verifyFormat(R"(catch (TestException) when (innerFinallyExecuted))", Style); verifyFormat(R"(private float[,] Values;)", Style); verifyFormat(R"(Result this[Index x] => Foo(x);)", Style); - verifyFormat(R"(class ItemFactory where T : new() {})", Style); Style.SpacesInSquareBrackets = true; verifyFormat(R"(private float[ , ] Values;)", Style); @@ -673,5 +672,22 @@ if (someThings[i][j][k].Contains(myThing)) { Style); } +TEST_F(FormatTestCSharp, CSharpGenericTypeConstraints) { + FormatStyle Style = getGoogleStyle(FormatStyle::LK_CSharp); + + verifyFormat(R"(// +class ItemFactory + where T : new() {})", Style); + + verifyFormat(R"(// +class Dictionary + where TKey : IComparable + where TVal : IMyInterface { + public void MyMethod(T t) + where T : IMyInterface { doThing(); } +})", + Style); +} + } // namespace format } // end namespace clang