Page MenuHomePhabricator

[clang-format] Support lightweight Objective-C generics
ClosedPublic

Authored by benhamilton on Apr 2 2018, 1:49 PM.

Details

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

Diff Detail

Event Timeline

benhamilton created this revision.Apr 2 2018, 1:49 PM
djasper added inline comments.Apr 3 2018, 11:44 PM
lib/Format/UnwrappedLineParser.cpp
2135

The UnwrappedLineParser is very much about error recovery. Implemented like this, it will consume the rest of the file if someone forgets to add the closing ">", which can very easily happen when formatting in an editor while coding.

Are there things (e.g. semicolons and braces) that clearly point to this having happened so that clang-format can recover?

2136

I think we generally prefer:

++NumOpenAngles;
2138

I am slightly worried that this loop might be getting more complicated and it will be harder to determine that NumOpenAngles cannot be 0 here. Might be worth adding an assert. But might be overkill.

2165

Are there more places where we might want to parse a lightweight generic list? If this is going to remain they only instance, I think I'd prefer a local lambda or just inlining the code. But up to you.

2166

nitpick: As the comment refers to this following block as well, I'd remove the empty line.

benhamilton marked 3 inline comments as done.

Thanks, fixed!

lib/Format/UnwrappedLineParser.cpp
2135

Good call. I had modeled this after UnwrappedLineParser::parseObjCProtocolList(), which has the same issue.

Fixed both that method and this one to exit early on semicolon, l_brace, and @end.

2136

Fixed.

2138

Assert added.

2165

It's just the one place, so inlined the code.

Protocol lists can appear both after @interface and @protocol, but lightweight generics are only for @interface. I confirmed this with a quick test — changing @protocol to @interface in the example below allows it to compile:

% cat test.m
#import <Foundation/Foundation.h>

@protocol Foo <KeyType> : NSObject
- (id)foo:(KeyType)aKey;
@end

lang -c test.m                      
cattest.m:3:16: error: cannot find protocol declaration for 'KeyType'
@protocol Foo <KeyType> : NSObject
               ^
test.m:3:25: error: expected identifier or '('
@protocol Foo <KeyType> : NSObject
                        ^
test.m:4:12: error: expected a type
- (id)foo:(KeyType)aKey;
           ^
2166

Done.

djasper accepted this revision.Apr 5 2018, 8:14 AM

Looks good, thank you!

This revision is now accepted and ready to land.Apr 5 2018, 8:14 AM
This revision was automatically updated to reflect the committed changes.