Index: lib/Format/ContinuationIndenter.cpp =================================================================== --- lib/Format/ContinuationIndenter.cpp +++ lib/Format/ContinuationIndenter.cpp @@ -889,8 +889,8 @@ if ((PreviousNonComment && (PreviousNonComment->ClosesTemplateDeclaration || PreviousNonComment->isOneOf( - TT_AttributeParen, TT_FunctionAnnotationRParen, TT_JavaAnnotation, - TT_LeadingJavaAnnotation))) || + TT_AttributeParen, TT_AttributeSquare, TT_FunctionAnnotationRParen, + TT_JavaAnnotation, TT_LeadingJavaAnnotation))) || (!Style.IndentWrappedFunctionNames && NextNonComment->isOneOf(tok::kw_operator, TT_FunctionDeclarationName))) return std::max(State.Stack.back().LastSpace, State.Stack.back().Indent); Index: lib/Format/FormatToken.h =================================================================== --- lib/Format/FormatToken.h +++ lib/Format/FormatToken.h @@ -29,7 +29,9 @@ #define LIST_TOKEN_TYPES \ TYPE(ArrayInitializerLSquare) \ TYPE(ArraySubscriptLSquare) \ + TYPE(AttributeColon) \ TYPE(AttributeParen) \ + TYPE(AttributeSquare) \ TYPE(BinaryOperator) \ TYPE(BitFieldColon) \ TYPE(BlockComment) \ Index: lib/Format/TokenAnnotator.cpp =================================================================== --- lib/Format/TokenAnnotator.cpp +++ lib/Format/TokenAnnotator.cpp @@ -320,13 +320,62 @@ return false; } + const FormatToken *parseCpp11AttributeSpecifier(const FormatToken &Tok) { + if (!Style.isCpp() || !Tok.startsSequence(tok::l_square, tok::l_square)) + return nullptr; + const FormatToken *AttrTok = Tok.Next->Next; + if (!AttrTok) + return nullptr; + // C++17 '[[using namespace: foo, bar(baz, blech)]]' + bool IsUsingNamespace = + AttrTok->startsSequence(tok::kw_using, tok::identifier, tok::colon); + if (IsUsingNamespace) { + AttrTok = AttrTok->Next->Next->Next; + } + auto parseCpp11Attribute = [](const FormatToken &Tok, + bool AllowNamespace) -> const FormatToken * { + if (!Tok.isOneOf(tok::identifier, tok::ellipsis)) + return nullptr; + const FormatToken *AttrTok = Tok.Next; + if (!AttrTok) + return nullptr; + if (AllowNamespace && + AttrTok->startsSequence(tok::coloncolon, tok::identifier)) { + AttrTok = AttrTok->Next->Next; + } + if (!AttrTok) + return nullptr; + if (AttrTok->is(tok::l_paren)) { + const FormatToken *ParamToken = AttrTok->Next; + while (ParamToken && ParamToken->isNot(tok::r_paren)) + ParamToken = ParamToken->Next; + if (!ParamToken) + return nullptr; + AttrTok = ParamToken->Next; + } + return AttrTok; + }; + while (AttrTok) { + AttrTok = parseCpp11Attribute(*AttrTok, !IsUsingNamespace); + if (!AttrTok || AttrTok->isNot(tok::comma)) + break; + AttrTok = AttrTok->Next; + } + if (AttrTok && AttrTok->startsSequence(tok::r_square, tok::r_square)) { + return AttrTok->Next; + } else { + return nullptr; + } + } + bool parseSquare() { if (!CurrentToken) return false; // A '[' could be an index subscript (after an identifier or after // ')' or ']'), it could be the start of an Objective-C method - // expression, or it could the start of an Objective-C array literal. + // expression, it could the start of an Objective-C array literal, + // or it could be a C++ attribute specifier [[foo::bar]]. FormatToken *Left = CurrentToken->Previous; Left->ParentBracket = Contexts.back().ContextKind; FormatToken *Parent = Left->getPreviousNonComment(); @@ -339,6 +388,23 @@ (Contexts.back().CanBeExpression || Contexts.back().IsExpression || Contexts.back().InTemplateArgument); + const FormatToken *Cpp11AttributeSpecifierClosingRSquare = + parseCpp11AttributeSpecifier(*Left); + if (Cpp11AttributeSpecifierClosingRSquare) { + Left->Type = TT_AttributeSquare; + while (CurrentToken != Cpp11AttributeSpecifierClosingRSquare) { + if (CurrentToken->is(tok::colon)) { + CurrentToken->Type = TT_AttributeColon; + } + next(); + } + CurrentToken->Type = TT_AttributeSquare; + CurrentToken->MatchingParen = Left; + Left->MatchingParen = CurrentToken; + next(); + return true; + } + bool StartsObjCMethodExpr = !CppArrayTemplates && Style.isCpp() && Contexts.back().CanBeExpression && Left->isNot(TT_LambdaLSquare) && @@ -2118,7 +2184,7 @@ return 35; if (!Right.isOneOf(TT_ObjCMethodExpr, TT_LambdaLSquare, TT_ArrayInitializerLSquare, - TT_DesignatedInitializerLSquare)) + TT_DesignatedInitializerLSquare, TT_AttributeSquare)) return 500; } @@ -2369,11 +2435,12 @@ Style)) || (Style.SpacesInSquareBrackets && Right.MatchingParen->isOneOf(TT_ArraySubscriptLSquare, - TT_StructuredBindingLSquare))); + TT_StructuredBindingLSquare)) || + Right.MatchingParen->is(TT_AttributeParen)); if (Right.is(tok::l_square) && !Right.isOneOf(TT_ObjCMethodExpr, TT_LambdaLSquare, TT_DesignatedInitializerLSquare, - TT_StructuredBindingLSquare) && + TT_StructuredBindingLSquare, TT_AttributeSquare) && !Left.isOneOf(tok::numeric_constant, TT_DictLiteral)) return false; if (Left.is(tok::l_brace) && Right.is(tok::r_brace)) @@ -2385,7 +2452,8 @@ if (Left.is(TT_BlockComment)) return !Left.TokenText.endswith("=*/"); if (Right.is(tok::l_paren)) { - if (Left.is(tok::r_paren) && Left.is(TT_AttributeParen)) + if ((Left.is(tok::r_paren) && Left.is(TT_AttributeParen)) || + (Left.is(tok::r_square) && Left.is(TT_AttributeSquare))) return true; return Line.Type == LT_ObjCDecl || Left.is(tok::semi) || (Style.SpaceBeforeParens != FormatStyle::SBPO_Never && @@ -2596,6 +2664,8 @@ return false; if (Right.is(TT_DictLiteral)) return Style.SpacesInContainerLiterals; + if (Right.is(TT_AttributeColon)) + return false; return true; } if (Left.is(TT_UnaryOperator)) @@ -2956,7 +3026,8 @@ return !Right.isOneOf(tok::l_brace, tok::semi, tok::equal, tok::l_paren, tok::less, tok::coloncolon); - if (Right.is(tok::kw___attribute)) + if (Right.is(tok::kw___attribute) || + (Right.is(tok::l_square) && Right.is(TT_AttributeSquare))) return true; if (Left.is(tok::identifier) && Right.is(tok::string_literal)) @@ -2997,6 +3068,9 @@ (Style.BreakBeforeBinaryOperators == FormatStyle::BOS_None || Left.getPrecedence() == prec::Assignment)) return true; + if ((Left.is(TT_AttributeSquare) && Right.is(tok::l_square)) || + (Left.is(tok::r_square) && Right.is(TT_AttributeSquare))) + return false; return Left.isOneOf(tok::comma, tok::coloncolon, tok::semi, tok::l_brace, tok::kw_class, tok::kw_struct, tok::comment) || Right.isMemberAccess() || Index: unittests/Format/FormatTest.cpp =================================================================== --- unittests/Format/FormatTest.cpp +++ unittests/Format/FormatTest.cpp @@ -6059,6 +6059,16 @@ AfterType); } +TEST_F(FormatTest, UnderstandsSquareAttributes) { + verifyFormat("SomeType s [[unused]] (InitValue);"); + verifyFormat("SomeType s [[gnu::unused]] (InitValue);"); + verifyFormat("SomeType s [[using gnu: unused]] (InitValue);"); + verifyFormat("[[gsl::suppress(\"clang-tidy-check-name\")]] void f() {}"); + verifyFormat("void f() [[deprecated(\"so sorry\")]];"); + verifyFormat("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n" + " [[unused]] aaaaaaaaaaaaaaaaaaaaaaa(int i);"); +} + TEST_F(FormatTest, UnderstandsEllipsis) { verifyFormat("int printf(const char *fmt, ...);"); verifyFormat("template void Foo(Ts... ts) { Foo(ts...); }"); @@ -12080,6 +12090,36 @@ EXPECT_EQ(FormatStyle::LK_ObjC, guessLanguage("foo", "@interface Foo\n@end\n")); } +TEST_F(FormatTest, GuessLanguageWithCpp11AttributeSpecifiers) { + EXPECT_EQ(FormatStyle::LK_Cpp, guessLanguage("foo.h", "[[noreturn]];")); + EXPECT_EQ(FormatStyle::LK_ObjC, + guessLanguage("foo.h", "array[[calculator getIndex]];")); + EXPECT_EQ(FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[noreturn, deprecated(\"so sorry\")]];")); + EXPECT_EQ( + FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[noreturn, deprecated(\"gone, sorry\")]];")); + EXPECT_EQ(FormatStyle::LK_ObjC, + guessLanguage("foo.h", "[[noreturn foo] bar];")); + EXPECT_EQ(FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[clang::fallthrough]];")); + EXPECT_EQ(FormatStyle::LK_ObjC, + guessLanguage("foo.h", "[[clang:fallthrough] foo];")); + EXPECT_EQ(FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[gsl::suppress(\"type\")]];")); + EXPECT_EQ(FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[using clang: fallthrough]];")); + EXPECT_EQ(FormatStyle::LK_ObjC, + guessLanguage("foo.h", "[[abusing clang:fallthrough] bar];")); + EXPECT_EQ(FormatStyle::LK_Cpp, + guessLanguage("foo.h", "[[using gsl: suppress(\"type\")]];")); + EXPECT_EQ( + FormatStyle::LK_Cpp, + guessLanguage("foo.h", + "[[clang::callable_when(\"unconsumed\", \"unknown\")]]")); + EXPECT_EQ(FormatStyle::LK_Cpp, guessLanguage("foo.h", "[[foo::bar, ...]]")); +} + } // end namespace } // end namespace format } // end namespace clang