[clang] dcbcec4 - [clang-format] Handle C# generic type constraints
Jonathan Coe via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 19 05:56:18 PDT 2020
Author: Jonathan Coe
Date: 2020-03-19T12:56:08Z
New Revision: dcbcec4822f47ec5b638dd9c20dcebd464569dae
URL: https://github.com/llvm/llvm-project/commit/dcbcec4822f47ec5b638dd9c20dcebd464569dae
DIFF: https://github.com/llvm/llvm-project/commit/dcbcec4822f47ec5b638dd9c20dcebd464569dae.diff
LOG: [clang-format] Handle C# generic type constraints
Summary:
Treat each C# generic type constraint, `where T: ...`, as a line.
Add C# keyword: where
Add Token Types: CSharpGenericTypeConstraint, CSharpGenericTypeConstraintColon, CSharpGenericTypeConstraintComma.
This patch does not wrap generic type constraints well, that will be addressed in a follow up patch.
Reviewers: krasimir
Reviewed By: krasimir
Subscribers: cfe-commits, MyDeveloperDay
Tags: #clang-format, #clang
Differential Revision: https://reviews.llvm.org/D76367
Added:
Modified:
clang/lib/Format/FormatToken.h
clang/lib/Format/TokenAnnotator.cpp
clang/lib/Format/UnwrappedLineFormatter.cpp
clang/lib/Format/UnwrappedLineParser.cpp
clang/lib/Format/UnwrappedLineParser.h
clang/unittests/Format/FormatTestCSharp.cpp
Removed:
################################################################################
diff --git a/clang/lib/Format/FormatToken.h b/clang/lib/Format/FormatToken.h
index 1b885b518f0d..10a5f0e96f96 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 d546a9f7c606..7193c8e6de44 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 84ccbec2150d..a81d480c8e64 100644
--- a/clang/lib/Format/UnwrappedLineFormatter.cpp
+++ b/clang/lib/Format/UnwrappedLineFormatter.cpp
@@ -64,6 +64,8 @@ class LevelIndentTracker {
}
if (static_cast<int>(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 00447ebdf5a9..e2a6389cb26d 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 e184cf5354fd..42b8b51a37cc 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<T>`.
+ // 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 03ebe337e76c..9746f6e15322 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<T> 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<T>
+ where T : new() {})", Style);
+
+ verifyFormat(R"(//
+class Dictionary<TKey, TVal>
+ where TKey : IComparable<TKey>
+ where TVal : IMyInterface {
+ public void MyMethod<T>(T t)
+ where T : IMyInterface { doThing(); }
+})",
+ Style);
+}
+
} // namespace format
} // end namespace clang
More information about the cfe-commits
mailing list