Index: clang/lib/Format/ContinuationIndenter.h =================================================================== --- clang/lib/Format/ContinuationIndenter.h +++ clang/lib/Format/ContinuationIndenter.h @@ -228,8 +228,10 @@ /// The position of the last space on each level. /// /// Used e.g. to break like: + /// \code /// functionCall(Parameter, otherCall( /// OtherParameter)); + /// \endcode unsigned LastSpace; /// If a block relative to this parenthesis level gets wrapped, indent Index: clang/lib/Format/ContinuationIndenter.cpp =================================================================== --- clang/lib/Format/ContinuationIndenter.cpp +++ clang/lib/Format/ContinuationIndenter.cpp @@ -1235,8 +1235,9 @@ (PreviousNonComment->ClosesTemplateDeclaration || PreviousNonComment->ClosesRequiresClause || PreviousNonComment->isOneOf( - TT_AttributeParen, TT_AttributeSquare, TT_FunctionAnnotationRParen, - TT_JavaAnnotation, TT_LeadingJavaAnnotation))) || + TT_AttributeParen, TT_AttributeMacro, TT_AttributeSquare, + TT_FunctionAnnotationRParen, TT_JavaAnnotation, + TT_LeadingJavaAnnotation))) || (!Style.IndentWrappedFunctionNames && NextNonComment->isOneOf(tok::kw_operator, TT_FunctionDeclarationName))) { return std::max(CurrentState.LastSpace, CurrentState.Indent); Index: clang/lib/Format/TokenAnnotator.cpp =================================================================== --- clang/lib/Format/TokenAnnotator.cpp +++ clang/lib/Format/TokenAnnotator.cpp @@ -374,7 +374,7 @@ // Infer the role of the l_paren based on the previous token if we haven't // detected one yet. if (PrevNonComment && OpeningParen.is(TT_Unknown)) { - if (PrevNonComment->is(tok::kw___attribute)) { + if (PrevNonComment->isOneOf(tok::kw___attribute, TT_AttributeMacro)) { OpeningParen.setType(TT_AttributeParen); } else if (PrevNonComment->isOneOf(TT_TypenameMacro, tok::kw_decltype, tok::kw_typeof, @@ -4510,7 +4510,9 @@ if (Line.Type == LT_ObjCMethodDecl) { if (Left.is(TT_ObjCMethodSpecifier)) return true; - if (Left.is(tok::r_paren) && canBeObjCSelectorComponent(Right)) { + // Apply this logic for parens that are not function attribute macros. + if (Left.is(tok::r_paren) && Left.isNot(TT_AttributeParen) && + canBeObjCSelectorComponent(Right)) { // Don't space between ')' and or ')' and 'new'. 'new' is not a // keyword in Objective-C, and '+ (instancetype)new;' is a standard class // method declaration. @@ -5033,8 +5035,10 @@ } // Ensure wrapping after __attribute__((XX)) and @interface etc. - if (Left.is(TT_AttributeParen) && Right.is(TT_ObjCDecl)) + if (Left.isOneOf(TT_AttributeMacro, TT_AttributeParen) && + Right.is(TT_ObjCDecl)) { return true; + } if (Left.is(TT_LambdaLBrace)) { if (IsFunctionArgument(Left) && @@ -5464,10 +5468,13 @@ tok::less, tok::coloncolon); } - if (Right.is(tok::kw___attribute) || - (Right.is(tok::l_square) && Right.is(TT_AttributeSquare))) { + // Breaking before a middle-of-line attribute macro is valid. + if (Right.isOneOf(tok::kw___attribute, TT_AttributeMacro)) + return !Left.is(TT_AttributeSquare); + + // Don't split `[[` on C++ attributes. + if (Right.is(tok::l_square) && Right.is(TT_AttributeSquare)) return !Left.is(TT_AttributeSquare); - } if (Left.is(tok::identifier) && Right.is(tok::string_literal)) return true; Index: clang/unittests/Format/FormatTestObjC.cpp =================================================================== --- clang/unittests/Format/FormatTestObjC.cpp +++ clang/unittests/Format/FormatTestObjC.cpp @@ -1493,7 +1493,10 @@ " [obj func:arg2];"); } -TEST_F(FormatTestObjC, Attributes) { +TEST_F(FormatTestObjC, AttributesOnObjCDecl) { + Style.AttributeMacros.push_back("ATTRIBUTE_MACRO"); + + // Check '__attribute__' macro directly. verifyFormat("__attribute__((objc_subclassing_restricted))\n" "@interface Foo\n" "@end"); @@ -1503,6 +1506,213 @@ verifyFormat("__attribute__((objc_subclassing_restricted))\n" "@implementation Foo\n" "@end"); + + // Check AttributeMacro gets treated the same, with or without parentheses. + verifyFormat("ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + + // Indenter also needs to understand multiple attribute macros. + // Try each of the three kinds paired with each of the other kind. + + // Column limit, but no reflow. + verifyFormat("ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X)) ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO __attribute__((X))\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X)) ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO(X) __attribute__((X))\n" + "@interface Foo\n" + "@end"); + + // Column limit that requires reflow. + Style.ColumnLimit = 30; + verifyFormat("ATTRIBUTE_MACRO(X)\n" + "ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO\n" + "ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X))\n" + "ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO\n" + "__attribute__((X))\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X))\n" + "ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO(X)\n" + "__attribute__((X))\n" + "@interface Foo\n" + "@end"); + + // No column limit + Style.ColumnLimit = 0; + verifyFormat("ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X)) ATTRIBUTE_MACRO\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO __attribute__((X))\n" + "@interface Foo\n" + "@end"); + verifyFormat("__attribute__((X)) ATTRIBUTE_MACRO(X)\n" + "@interface Foo\n" + "@end"); + verifyFormat("ATTRIBUTE_MACRO(X) __attribute__((X))\n" + "@interface Foo\n" + "@end"); +} + +TEST_F(FormatTestObjC, AttributesOnObjCMethodDecl) { + Style.AttributeMacros.push_back("ATTRIBUTE_MACRO"); + + // Check '__attribute__' macro directly. + verifyFormat("- (id)init __attribute__((objc_designated_initializer));"); + + // Check AttributeMacro gets treated the same, with or without parentheses. + verifyFormat("- (id)init ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO(X);"); + + // Indenter also needs to understand multiple attribute macros. + + // Column limit (default), but no reflow. + verifyFormat("- (id)init ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init __attribute__((X)) ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO __attribute__((X));"); + verifyFormat("- (id)init __attribute__((X)) ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init ATTRIBUTE_MACRO(X) __attribute__((X));"); + + // Column limit that requires reflow. + Style.ColumnLimit = 30; + + // Reflow after method name. + verifyFormat("- (id)initWithReallyLongName\n" + " __attribute__((X))\n" + " ATTRIBUTE_MACRO;"); + verifyFormat("- (id)initWithReallyLongName\n" + " ATTRIBUTE_MACRO(X)\n" + " ATTRIBUTE_MACRO;"); + verifyFormat("- (id)initWithReallyLongName\n" + " ATTRIBUTE_MACRO\n" + " ATTRIBUTE_MACRO;"); + // Reflow after first macro. + // FIXME: these should indent but don't. +#if 0 + verifyFormat("- (id)init ATTRIBUTE_MACRO(X)\n" + " ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO\n" + " ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init __attribute__((X))\n" + " ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO\n" + " __attribute__((X));"); + verifyFormat("- (id)init __attribute__((X))\n" + " ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init ATTRIBUTE_MACRO(X)\n" + " __attribute__((X));"); +#endif + + // No column limit. + Style.ColumnLimit = 0; + verifyFormat("- (id)init ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init __attribute__((X)) ATTRIBUTE_MACRO;"); + verifyFormat("- (id)init ATTRIBUTE_MACRO __attribute__((X));"); + verifyFormat("- (id)init __attribute__((X)) ATTRIBUTE_MACRO(X);"); + verifyFormat("- (id)init ATTRIBUTE_MACRO(X) __attribute__((X));"); +} + +TEST_F(FormatTestObjC, AttributesOnObjCProperty) { + Style.AttributeMacros.push_back("ATTRIBUTE_MACRO"); + + // Check '__attribute__' macro directly. + verifyFormat("@property(weak) id delegate " + "__attribute__((objc_designated_initializer));"); + + // Check AttributeMacro gets treated the same, with or without parentheses. + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO;"); + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO(X);"); + + // Indenter also needs to understand multiple attribute macros. + + // Column limit (default), but no reflow. + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO;"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X);"); + verifyFormat( + "@property(weak) id delegate __attribute__((X)) ATTRIBUTE_MACRO;"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO __attribute__((X));"); + verifyFormat( + "@property(weak) id delegate __attribute__((X)) ATTRIBUTE_MACRO(X);"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO(X) __attribute__((X));"); + + // Column limit that requires reflow. + Style.ColumnLimit = 50; + + // Reflow after method name. + verifyFormat("@property(weak) id delegateWithLongName\n" + " __attribute__((X)) ATTRIBUTE_MACRO;"); + verifyFormat("@property(weak) id delegateWithLongName\n" + " ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO;"); + verifyFormat("@property(weak) id delegateWithLongName\n" + " ATTRIBUTE_MACRO ATTRIBUTE_MACRO;"); + // Reflow after first macro. + // FIXME: these should indent but don't. + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO(X)\n" + "ATTRIBUTE_MACRO;"); + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO\n" + "ATTRIBUTE_MACRO(X);"); + verifyFormat("@property(weak) id delegate __attribute__((X))\n" + "ATTRIBUTE_MACRO;"); + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO\n" + "__attribute__((X));"); + verifyFormat("@property(weak) id delegate __attribute__((X))\n" + "ATTRIBUTE_MACRO(X);"); + verifyFormat("@property(weak) id delegate ATTRIBUTE_MACRO(X)\n" + "__attribute__((X));"); + + // No column limit. + Style.ColumnLimit = 0; + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO(X) ATTRIBUTE_MACRO;"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO ATTRIBUTE_MACRO(X);"); + verifyFormat( + "@property(weak) id delegate __attribute__((X)) ATTRIBUTE_MACRO;"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO __attribute__((X));"); + verifyFormat( + "@property(weak) id delegate __attribute__((X)) ATTRIBUTE_MACRO(X);"); + verifyFormat( + "@property(weak) id delegate ATTRIBUTE_MACRO(X) __attribute__((X));"); } } // end namespace Index: clang/unittests/Format/TokenAnnotatorTest.cpp =================================================================== --- clang/unittests/Format/TokenAnnotatorTest.cpp +++ clang/unittests/Format/TokenAnnotatorTest.cpp @@ -1543,6 +1543,116 @@ EXPECT_TOKEN(Tokens[13], tok::arrow, TT_Unknown); } +TEST_F(TokenAnnotatorTest, UnderstandsAttributeMacros) { + // '__attribute__' has special handling. + auto Tokens = annotate("__attribute__(X) void Foo(void);"); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::kw___attribute, TT_Unknown); + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[3], tok::r_paren, TT_AttributeParen); + + // Generic macro has no special handling in this location. + Tokens = annotate("A(X) void Foo(void);"); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::identifier, TT_Unknown); + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_Unknown); + + // Add a custom AttributeMacro. Test that it has the same behavior. + FormatStyle Style = getLLVMStyle(); + Style.AttributeMacros.push_back("A"); + + // An "AttributeMacro" gets annotated like '__attribute__'. + Tokens = annotate("A(X) void Foo(void);", Style); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::identifier, TT_AttributeMacro); + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[3], tok::r_paren, TT_AttributeParen); +} + +TEST_F(TokenAnnotatorTest, UnderstandsAttributeMacrosOnObjCDecl) { + // '__attribute__' has special handling. + auto Tokens = annotate("__attribute__(X) @interface Foo"); + ASSERT_EQ(Tokens.size(), 8u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::kw___attribute, TT_Unknown); + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[3], tok::r_paren, TT_AttributeParen); + + // Generic macro has no special handling in this location. + Tokens = annotate("A(X) @interface Foo"); + ASSERT_EQ(Tokens.size(), 8u) << Tokens; + // Note: Don't check token-type as a random token in this position is hard to + // reason about. + EXPECT_TOKEN_KIND(Tokens[0], tok::identifier); + EXPECT_TOKEN_KIND(Tokens[1], tok::l_paren); + + // Add a custom AttributeMacro. Test that it has the same behavior. + FormatStyle Style = getLLVMStyle(); + Style.AttributeMacros.push_back("A"); + + // An "AttributeMacro" gets annotated like '__attribute__'. + Tokens = annotate("A(X) @interface Foo", Style); + ASSERT_EQ(Tokens.size(), 8u) << Tokens; + EXPECT_TOKEN(Tokens[0], tok::identifier, TT_AttributeMacro); + EXPECT_TOKEN(Tokens[1], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[3], tok::r_paren, TT_AttributeParen); +} + +TEST_F(TokenAnnotatorTest, UnderstandsAttributeMacrosOnObjCMethodDecl) { + // '__attribute__' has special handling. + auto Tokens = annotate("- (id)init __attribute__(X);"); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[5], tok::kw___attribute, TT_Unknown); + EXPECT_TOKEN(Tokens[6], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[8], tok::r_paren, TT_AttributeParen); + + // Generic macro has no special handling in this location. + Tokens = annotate("- (id)init A(X);"); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + // Note: Don't check token-type as a random token in this position is hard to + // reason about. + EXPECT_TOKEN_KIND(Tokens[5], tok::identifier); + EXPECT_TOKEN_KIND(Tokens[6], tok::l_paren); + + // Add a custom AttributeMacro. Test that it has the same behavior. + FormatStyle Style = getLLVMStyle(); + Style.AttributeMacros.push_back("A"); + + // An "AttributeMacro" gets annotated like '__attribute__'. + Tokens = annotate("- (id)init A(X);", Style); + ASSERT_EQ(Tokens.size(), 11u) << Tokens; + EXPECT_TOKEN(Tokens[5], tok::identifier, TT_AttributeMacro); + EXPECT_TOKEN(Tokens[6], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[8], tok::r_paren, TT_AttributeParen); +} + +TEST_F(TokenAnnotatorTest, UnderstandsAttributeMacrosOnObjCProperty) { + // '__attribute__' has special handling. + auto Tokens = annotate("@property(weak) id delegate __attribute__(X);"); + ASSERT_EQ(Tokens.size(), 13u) << Tokens; + EXPECT_TOKEN(Tokens[7], tok::kw___attribute, TT_Unknown); + EXPECT_TOKEN(Tokens[8], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[10], tok::r_paren, TT_AttributeParen); + + // Generic macro has no special handling in this location. + Tokens = annotate("@property(weak) id delegate A(X);"); + ASSERT_EQ(Tokens.size(), 13u) << Tokens; + // Note: Don't check token-type as a random token in this position is hard to + // reason about. + EXPECT_TOKEN_KIND(Tokens[7], tok::identifier); + EXPECT_TOKEN_KIND(Tokens[8], tok::l_paren); + + // Add a custom AttributeMacro. Test that it has the same behavior. + FormatStyle Style = getLLVMStyle(); + Style.AttributeMacros.push_back("A"); + + // An "AttributeMacro" gets annotated like '__attribute__'. + Tokens = annotate("@property(weak) id delegate A(X);", Style); + ASSERT_EQ(Tokens.size(), 13u) << Tokens; + EXPECT_TOKEN(Tokens[7], tok::identifier, TT_AttributeMacro); + EXPECT_TOKEN(Tokens[8], tok::l_paren, TT_AttributeParen); + EXPECT_TOKEN(Tokens[10], tok::r_paren, TT_AttributeParen); +} + TEST_F(TokenAnnotatorTest, UnderstandsVerilogOperators) { auto Annotate = [this](llvm::StringRef Code) { return annotate(Code, getLLVMStyle(FormatStyle::LK_Verilog));