Skip to content

Commit 24b5266

Browse files
committed
[Format/ObjC] Correctly handle base class with lightweight generics and protocol
ClangFormat does not correctly handle an Objective-C interface declaration with both lightweight generics and a protocol conformance. This simple example: ``` @interface Foo : Bar <Baz> <Blech> @EnD ``` means `Foo` extends `Bar` (a lightweight generic class whose type parameter is `Baz`) and also conforms to the protocol `Blech`. ClangFormat should not apply any changes to the above example, but instead it currently formats it quite poorly: ``` @interface Foo : Bar <Baz> <Blech> @EnD ``` The bug is that `UnwrappedLineParser` assumes an open-angle bracket after a base class name is a protocol list, but it can also be a lightweight generic specification. This diff fixes the bug by factoring out the logic to parse lightweight generics so it can apply both to the declared class as well as the base class. Test Plan: New tests added. Ran tests with: % ninja FormatTests && ./tools/clang/unittests/Format/FormatTests Confirmed tests failed before diff and passed after diff. Reviewed By: sammccall, MyDeveloperDay Differential Revision: https://reviews.llvm.org/D89496
1 parent f0f3d1b commit 24b5266

File tree

3 files changed

+48
-22
lines changed

3 files changed

+48
-22
lines changed

clang/lib/Format/UnwrappedLineParser.cpp

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,32 +2612,15 @@ void UnwrappedLineParser::parseObjCInterfaceOrImplementation() {
26122612
// @interface can be followed by a lightweight generic
26132613
// specialization list, then either a base class or a category.
26142614
if (FormatTok->Tok.is(tok::less)) {
2615-
// Unlike protocol lists, generic parameterizations support
2616-
// nested angles:
2617-
//
2618-
// @interface Foo<ValueType : id <NSCopying, NSSecureCoding>> :
2619-
// NSObject <NSCopying, NSSecureCoding>
2620-
//
2621-
// so we need to count how many open angles we have left.
2622-
unsigned NumOpenAngles = 1;
2623-
do {
2624-
nextToken();
2625-
// Early exit in case someone forgot a close angle.
2626-
if (FormatTok->isOneOf(tok::semi, tok::l_brace) ||
2627-
FormatTok->Tok.isObjCAtKeyword(tok::objc_end))
2628-
break;
2629-
if (FormatTok->Tok.is(tok::less))
2630-
++NumOpenAngles;
2631-
else if (FormatTok->Tok.is(tok::greater)) {
2632-
assert(NumOpenAngles > 0 && "'>' makes NumOpenAngles negative");
2633-
--NumOpenAngles;
2634-
}
2635-
} while (!eof() && NumOpenAngles != 0);
2636-
nextToken(); // Skip '>'.
2615+
parseObjCLightweightGenerics();
26372616
}
26382617
if (FormatTok->Tok.is(tok::colon)) {
26392618
nextToken();
26402619
nextToken(); // base class name
2620+
// The base class can also have lightweight generics applied to it.
2621+
if (FormatTok->Tok.is(tok::less)) {
2622+
parseObjCLightweightGenerics();
2623+
}
26412624
} else if (FormatTok->Tok.is(tok::l_paren))
26422625
// Skip category, if present.
26432626
parseParens();
@@ -2658,6 +2641,32 @@ void UnwrappedLineParser::parseObjCInterfaceOrImplementation() {
26582641
parseObjCUntilAtEnd();
26592642
}
26602643

2644+
void UnwrappedLineParser::parseObjCLightweightGenerics() {
2645+
assert(FormatTok->Tok.is(tok::less));
2646+
// Unlike protocol lists, generic parameterizations support
2647+
// nested angles:
2648+
//
2649+
// @interface Foo<ValueType : id <NSCopying, NSSecureCoding>> :
2650+
// NSObject <NSCopying, NSSecureCoding>
2651+
//
2652+
// so we need to count how many open angles we have left.
2653+
unsigned NumOpenAngles = 1;
2654+
do {
2655+
nextToken();
2656+
// Early exit in case someone forgot a close angle.
2657+
if (FormatTok->isOneOf(tok::semi, tok::l_brace) ||
2658+
FormatTok->Tok.isObjCAtKeyword(tok::objc_end))
2659+
break;
2660+
if (FormatTok->Tok.is(tok::less))
2661+
++NumOpenAngles;
2662+
else if (FormatTok->Tok.is(tok::greater)) {
2663+
assert(NumOpenAngles > 0 && "'>' makes NumOpenAngles negative");
2664+
--NumOpenAngles;
2665+
}
2666+
} while (!eof() && NumOpenAngles != 0);
2667+
nextToken(); // Skip '>'.
2668+
}
2669+
26612670
// Returns true for the declaration/definition form of @protocol,
26622671
// false for the expression form.
26632672
bool UnwrappedLineParser::parseObjCProtocol() {

clang/lib/Format/UnwrappedLineParser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class UnwrappedLineParser {
118118
// parses the record as a child block, i.e. if the class declaration is an
119119
// expression.
120120
void parseRecord(bool ParseAsExpr = false);
121+
void parseObjCLightweightGenerics();
121122
void parseObjCMethod();
122123
void parseObjCProtocolList();
123124
void parseObjCUntilAtEnd();

clang/unittests/Format/FormatTestObjC.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,18 @@ TEST_F(FormatTestObjC, FormatObjCInterface) {
328328
"+ (id)init;\n"
329329
"@end");
330330

331+
verifyFormat("@interface Foo<Bar : Baz <Blech>> : Xyzzy <Corge> <Quux> {\n"
332+
" int _i;\n"
333+
"}\n"
334+
"+ (id)init;\n"
335+
"@end");
336+
337+
verifyFormat("@interface Foo : Bar <Baz> <Blech>\n"
338+
"@end");
339+
340+
verifyFormat("@interface Foo : Bar <Baz> <Blech, Xyzzy, Corge>\n"
341+
"@end");
342+
331343
verifyFormat("@interface Foo (HackStuff) {\n"
332344
" int _i;\n"
333345
"}\n"
@@ -413,6 +425,10 @@ TEST_F(FormatTestObjC, FormatObjCInterface) {
413425
" fffffffffffff,\n"
414426
" fffffffffffff> {\n"
415427
"}");
428+
verifyFormat("@interface ggggggggggggg\n"
429+
" : ggggggggggggg <ggggggggggggg>\n"
430+
" <ggggggggggggg>\n"
431+
"@end");
416432
}
417433

418434
TEST_F(FormatTestObjC, FormatObjCImplementation) {

0 commit comments

Comments
 (0)