]> granicus.if.org Git - clang/commitdiff
[clang-format] Support lightweight Objective-C generics
authorBen Hamilton <benhamilton@google.com>
Thu, 5 Apr 2018 15:26:25 +0000 (15:26 +0000)
committerBen Hamilton <benhamilton@google.com>
Thu, 5 Apr 2018 15:26:25 +0000 (15:26 +0000)
Summary:
Previously, `clang-format` didn't understand lightweight
Objective-C generics, which have the form:

```
@interface Foo <KeyType,
                ValueTypeWithConstraint : Foo,
AnotherValueTypeWithGenericConstraint: Bar<Baz>, ... > ...
```

The lightweight generic specifier list appears before the base
class, if present, but because it starts with < like the protocol
specifier list, `UnwrappedLineParser` was getting confused and
failed to parse interfaces with both generics and protocol lists:

```
@interface Foo <KeyType> : NSObject <NSCopying>
```

Since the parsed line would be incomplete, the format result
would be very confused (e.g., https://bugs.llvm.org/show_bug.cgi?id=24381).

This fixes the issue by explicitly parsing the ObjC lightweight
generic conformance list, so the line is fully parsed.

Fixes: https://bugs.llvm.org/show_bug.cgi?id=24381
Test Plan: New tests added. Ran tests with:
  % make -j16 FormatTests && ./tools/clang/unittests/Format/FormatTests

Reviewers: djasper, jolesiak

Reviewed By: djasper

Subscribers: klimek, cfe-commits

Differential Revision: https://reviews.llvm.org/D45185

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@329298 91177308-0d34-0410-b5e6-96231b3b80d8

lib/Format/UnwrappedLineParser.cpp
unittests/Format/FormatTestObjC.cpp

index 5ab5cc46cdcd9223be0e4e8917f0a638ffe9737c..be7e2bbabac14ba79c959dd75ba643f56a59e507 100644 (file)
@@ -2122,9 +2122,13 @@ void UnwrappedLineParser::parseRecord(bool ParseAsExpr) {
 
 void UnwrappedLineParser::parseObjCProtocolList() {
   assert(FormatTok->Tok.is(tok::less) && "'<' expected.");
-  do
+  do {
     nextToken();
-  while (!eof() && FormatTok->Tok.isNot(tok::greater));
+    // Early exit in case someone forgot a close angle.
+    if (FormatTok->isOneOf(tok::semi, tok::l_brace) ||
+        FormatTok->Tok.isObjCAtKeyword(tok::objc_end))
+      return;
+  } while (!eof() && FormatTok->Tok.isNot(tok::greater));
   nextToken(); // Skip '>'.
 }
 
@@ -2155,7 +2159,32 @@ void UnwrappedLineParser::parseObjCInterfaceOrImplementation() {
   nextToken();
   nextToken(); // interface name
 
-  // @interface can be followed by either a base class, or a category.
+  // @interface can be followed by a lightweight generic
+  // specialization list, then either a base class or a category.
+  if (FormatTok->Tok.is(tok::less)) {
+    // Unlike protocol lists, generic parameterizations support
+    // nested angles:
+    //
+    // @interface Foo<ValueType : id <NSCopying, NSSecureCoding>> :
+    //     NSObject <NSCopying, NSSecureCoding>
+    //
+    // so we need to count how many open angles we have left.
+    unsigned NumOpenAngles = 1;
+    do {
+      nextToken();
+      // Early exit in case someone forgot a close angle.
+      if (FormatTok->isOneOf(tok::semi, tok::l_brace) ||
+          FormatTok->Tok.isObjCAtKeyword(tok::objc_end))
+        break;
+      if (FormatTok->Tok.is(tok::less))
+        ++NumOpenAngles;
+      else if (FormatTok->Tok.is(tok::greater)) {
+        assert(NumOpenAngles > 0 && "'>' makes NumOpenAngles negative");
+        --NumOpenAngles;
+      }
+    } while (!eof() && NumOpenAngles != 0);
+    nextToken(); // Skip '>'.
+  }
   if (FormatTok->Tok.is(tok::colon)) {
     nextToken();
     nextToken(); // base class name
index 5ea05f45928993d7b009d4fbd580e824ba683d28..fe86cfe8ce9540953d2bc287f5b502e758bff127 100644 (file)
@@ -299,6 +299,18 @@ TEST_F(FormatTestObjC, FormatObjCInterface) {
                "+ (id)init;\n"
                "@end");
 
+  verifyFormat("@interface Foo <Baz : Blech> : Bar <Baz, Quux> {\n"
+               "  int _i;\n"
+               "}\n"
+               "+ (id)init;\n"
+               "@end");
+
+  verifyFormat("@interface Foo <Bar : Baz <Blech>> : Xyzzy <Corge> {\n"
+               "  int _i;\n"
+               "}\n"
+               "+ (id)init;\n"
+               "@end");
+
   verifyFormat("@interface Foo (HackStuff) {\n"
                "  int _i;\n"
                "}\n"