Add a style option to specify the order that property attributes should appear in ObjC property declarations (property attributes are things like`nonatomic, strong, nullable`).
Details
Diff Detail
Unit Tests
Event Timeline
Your review contains a change to clang/include/clang/Format/Format.h but does not contain an update to ClangFormatStyleOptions.rst
ClangFormatStyleOptions.rst is generated via clang/docs/tools/dump_format_style.py, please run this to regenerate the .rst
You can validate that the rst is valid by running.
./docs/tools/dump_format_style.py mkdir -p html /usr/bin/sphinx-build -n ./docs ./html
Some implementation notes:
- The implementation was modeled after QualifierAlignmentFixer and sortCppIncludes.
- the additions to the .bazel/.gn build files was done naively based on searching for where "QualifierFixerTest" appeared in the repo; I can't really test these. It looks right, but it's a guess.
- I considered creating a top-level style similar to QualifierAlignment/QualifierOrder; that one has a few pre-canned values (Leave, Left, Right, Custom), where Custom opens up the style QualifierOrder, an array of qualifier names.
- Pro: we could create a [Leave, LLVM] that would set them in the same order the DeclPrinter.cpp would dump them
- Pro: users probably would like not having to think about 18 different attributes (I did try to document them in groups to make them easier to find)
- Con: I couldn't find any style guides that provide a "standard order", so I don't know any other pre-canned settings that wouldn't just be an opinion of my own personal style. If there are, let me know and that might add weight to going this route.
I don't like that there are 18 attributes that users _probably_ want to specify for the style, so having some shortcut seems helpful. Perhaps I could just document it that way so it's easy to copy-paste? I also considered making it possible to have an alias so the style would only need to have a handful of them to get full coverage (eg, if you specify assign in the style, then the tool would infer you also mean to imply that the similar attributes retain, strong, copy, weak, unsafe_unretained would occupy the same "slot" unless you also specify them explicitly).
Update ClangFormatStyleOptions.rst as requested by the auto-comment (thanks auto-bot!).
LGTM, but someone other has to approve.
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
43–44 |
clang/unittests/Format/ObjCPropertyAttributeOrderFixerTest.cpp | ||
---|---|---|
77 | might it be a good idea to actually test some/all of the keywords you intend to support? just incase some of them are not actually tok::identifiers? |
Address review comments:
- fix some style
- add unit test for each ObjC attribute recognized by the compiler
- adjust the docs for the style-option to show a YAML example with all of them in a sane order (something people could copy-paste as a starter version)
clang/unittests/Format/ObjCPropertyAttributeOrderFixerTest.cpp | ||
---|---|---|
157 | I'm not a massive fan of writing looping logic in tests, I prefer just to lay them all out so when it fails I know precisely where and why. I don't want to have to debug a failure. I want it to be very obvious. My concern here in the first place is if someone makes one of your words a keyword further down the link, this is why you should have specific examples. |
Address review comments: unroll a loop in unit tests to explicitly test all the property attributes.
I don't have a problem with this, but I'm not an ObjC person so I can't really review from the validity of what its changing. If @owenpan and @HazardyKnusperkeks don't have an problem with this then I don't either as it likely won't impact most C++ users.
clang/lib/Format/QualifierAlignmentFixer.cpp | ||
---|---|---|
10 | This is an unrelated change, it wrong and that was probably my fault, we should do this as a NFC | |
llvm/utils/gn/secondary/clang/lib/Format/BUILD.gn | ||
23 | I never change these file either I think there is a bot or something that does this. | |
llvm/utils/gn/secondary/clang/unittests/Format/BUILD.gn | ||
39 | I never change these file either I think there is a bot or something that does this. | |
utils/bazel/llvm-project-overlay/clang/BUILD.bazel | ||
1304 | I never change these files I don't think we need to? |
may remove the other build system files, I don't think its our responsiblity to keep them upto date.
Rebased and adjusted docs to reflect that this patch would appear in clang-format 18 (not 17 now).
Removed extraneous comment change (will do NFC later for that)
Removed other build-system changes (as requested).
@MyDeveloperDay addressed your comments. Thanks!
Would love to get this in before Phabricator closes. Anything else I can do to help?
@jaredgrubb, a few quick comments:
- Please undo the (unrelated) changes to clang-formatted-files.txt.
- Add a warning to the option in Format.h. See here for an example.
- Rerun dump_format_style.py.
- Rebase if needed.
Address review feedback from @owenpan on Oct23. Thanks for helping me tune this patch!
See also D153228.
clang/include/clang/Format/Format.h | ||
---|---|---|
2998 | And reflow the comment. | |
clang/lib/Format/Format.cpp | ||
3488–3492 | Move down as it's Objective-C specific. (See below.) | |
3536 | ||
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
19 | Delete. | |
21–25 | Delete. | |
36 | ||
40 | Delete. | |
56 | I strongly suggest that we bail out as well if there are leading and/or trailing comments. | |
76 | No else after continue. | |
78 | Do we need trim()? | |
85–91 | Change: if (cond) { ... } else { return; } To: if (!cond) return; ... | |
87 | Ditto. | |
93–96 | Ditto. | |
98 | Can we assert PropertyAttributes.size() > 1 here? | |
105 | ||
105–107 | Start names with uppercase letters except for functions. | |
114 | Is it valid in Objective-C to have duplicate attributes? | |
121 | Is it possible that Indices becomes empty or has only one element after deduplication? | |
127 | Initialize with AppendAttribute. (See below.) | |
128–130 | ||
132–137 | Make it a lambda e.g. AppendAttribute. | |
155 | ||
191 | Must @property be the first non-comment tokens of an unwrapped line in Objective-C? And at most one @property per line? | |
clang/lib/Format/ObjCPropertyAttributeOrderFixer.h | ||
22 | Delete. | |
29 | Delete. (See comments in ObjCPropertyAttributeOrderFixer.cpp.) | |
45–49 | Move up to make it private. |
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
105 | Oops! Please ignore. |
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
185 | Why not InPPDirective? |
Addressing all review comments to date.
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
56 | I'm ok with that! In our codebase we have some code like this: @property (/*nullable*/ readonly, strong) Thing *thing; Nullablility annotations are all-or-nothing in a header file (putting it once triggers clang to complain about it missing in other eligible places). We have places where a dev wanted to add the attribute to one line but didn't want to commit to auditing the whole header. Their comment is a hint to the lucky person who does that work one day. I thought it would be easy to handle a leading/trailing comment in this patch, but I am very ok with skipping these lines completely as you suggest. Conservative is probably safer! I've revised the tests to affirm that any comments get skipped now. | |
78 | I removed both trim() and my tests still pass. I wasn't fully sure when trim would help, but it doesn't seem to in this case. | |
98 | We shouldn't _assert_, as it is valid to have just one. But I can add an early-return for that though. (I'll also early-return if it's zero, which is also legal, eg @property () int x) | |
114 | It's silly, but clang doesn't seem to care. I tried this, so duplicate was ok, but contradiction was flagged: @property (strong, nonatomic, nonatomic) X *X; // duplicate, but no error @property (strong, nonatomic, weak) Y *y; // error: Property attributes 'strong' and 'weak' are mutually exclusive I wasn't sure whether to do this, but went that way since that's what "sort include files" did. However, it seems like an odd corner case so I'd be ok removing this uniquing if you prefer. | |
121 | It is possible to reduce to one element (I have a unit test case for that scenario (a, a)). I don't see how std::unique could take a non-empty list and output an empty list, so I'll claim "no" on the empty part. | |
185 | I copy-pasted this from LeftRightQualifierAlignmentFixer::analyze, which I used as a template since I'm still getting used to the codebase. I wasn't sure whether this was important, so I left it in. But I don't think I have a good reason. I've added a new test case SortsInPPDirective that spot-checks some macro definition examples (which did fail unless this Line->InPPDirective check was removed, as expected.). | |
191 | I can't think of any token that should precede a @property. Aka, I don't think there are any __attribute__ that can fill that slot. You could have @optional or @required floating around between property/method declarations. I've added a test-case for a @property that is preceded by these tokens and proved that the reordering is handled correctly. As for multiple properties in one line, I've never seen a codebase with that. clang-format does split them already. |
Adjusting the Style comments, since the behavior changed and were not updated to reflect that leading/trailing comments no longer have special handling.
From my perspective, this patch is ready to go! Please let me know if there's anything more I can do to improve the patch! (@owenpan thanks so much for your help so far!)
Thank you for your patience! There are still a few comments not done from the previous round.
Would this patch have a similar performance issue?
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
74 | ||
94 | ||
114 | We should keep the duplicates if clang accepts them. What happens to e.g. @property(x=a, y=b, x=a, y=c)? | |
144 | ||
185 | What about Line->Type != LT_ObjCProperty I suggested? | |
186 | ||
191 | I asked the questions because if yes, we could move on to the next line before hitting the last token of the current line. |
BTW, it may be simpler and more efficient to use a set (e.g. llvm::SmallSet) for Indices, especially if we don't need/want to handle duplicate attributes that have a value. (See D150083#inline-1551778.)
I appreciate the help :) I'm excited to get this in!
Would this patch have a similar performance issue?
Reading through the issue, I don't think so. As I understand that issue, the scale-problem is due to how the passes interact with one another as a whole (since that setting creates a Pass for _each_ qualifier). This ObjC pass uses only ONE pass to handle the ordering of all property attributes. I intentionally did not copy that part of the Qualifier pass because it didn't seem to actually help my use-case, which is much narrower (primarily that I only have to operate on lines that look like properties, not in arbitrary places like function args and variable declarations, etc.).
Please correct me if I missed something about that patch that could apply.
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
114 | Looks like clang does not reject duplicates like that either, even though I can't imagine that it results in anything sane: @property (getter=Foo, getter=Bar) int x; I'll remove the de-dup code as you request -- it'll make the pass simpler and marginally faster :) For dup-cases where the values differ (eg, y=b, y=c), the pass will just stable-sort them. This is the simplest thing to do and IMO is good-enough as I think dup's are questionable anyway. I've added some test cases for this (and removed the original de-dup test cases). I'll add a couple unit tests to confirm this and include your example. | |
185 | Ah, I missed that suggestion. I don't have enough understanding of clang-format to say whether that would be correct, but I'll trust you :) When I add this check it doesn't break unit tests or cause any change-in-behavior on my ObjC test repo (~10K .h/.m files). So, it -seems- like this is a valid check to add, so I'll do it! | |
191 | In testing, it seems that some other pass (not sure which) will split the line before this new pass sees it. So if I can rely on that (I think I can?), then we can early-break like you propose. I've added a unit test to cover this scenario. |
LGTM except the final nits.
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
49 | ||
56 | ||
72 | ||
74 | ||
83–85 | Delete or assert instead. | |
98 | Now we can. 😉 | |
104 | You can assert the equality of the sizes before the if if you like. | |
137 | ||
166 | ||
176 | ||
185 |
If it does, then we probably have a bug in setting LT_ObjCProperty. 🙂 |
clang/lib/Format/ObjCPropertyAttributeOrderFixer.cpp | ||
---|---|---|
83–85 | This line is an optimization to return early in a case like, eg, when there is only one attribute: @property (nonatomic) int x; Therefore I don't think it makes sense to assert. I could remove this check but then it would do the sort on vector-of-one before it returns early. This statement is worth keeping. | |
98 | I want to keep the early-return above, which means any assert here would be redundant. Unless I'm missing what you're asking for completely? | |
104 | I'll remove the check; now that we don't de-duplicate, there's not really any way for the size to change. Good catch! | |
185 | hehe good point :) |
Address review comments (adding the 'asserts' and fixing up a couple other minor nits). Thanks @owenpan! I don't know if you're in the US, but if you are Happy Thanksgiving, and if you're not, then Happy Thursday :)
For some reason, I don't get email notifications anymore. :(
I'll land this for you in the next couple of days!
And reflow the comment.