Please use GitHub pull requests for new patches. Phabricator shutdown timeline
Changeset View
Standalone View
clang-tools-extra/clang-tidy/modernize/UseTrailingReturnTypeCheck.cpp
Show First 20 Lines • Show All 255 Lines • ▼ Show 20 Lines | Result = Result || hasAnyNestedLocalQualifiers( | ||||
Type->castAs<PointerType>()->getPointeeType()); | Type->castAs<PointerType>()->getPointeeType()); | ||||
if (Type->isReferenceType()) | if (Type->isReferenceType()) | ||||
Result = Result || hasAnyNestedLocalQualifiers( | Result = Result || hasAnyNestedLocalQualifiers( | ||||
Type->castAs<ReferenceType>()->getPointeeType()); | Type->castAs<ReferenceType>()->getPointeeType()); | ||||
return Result; | return Result; | ||||
} | } | ||||
SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange( | SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange( | ||||
const FunctionDecl &F, const ASTContext &Ctx, const SourceManager &SM, | const FunctionDecl &F, const TypeLoc &ReturnLoc, const ASTContext &Ctx, | ||||
const LangOptions &LangOpts) { | const SourceManager &SM, const LangOptions &LangOpts) { | ||||
// We start with the range of the return type and expand to neighboring | // We start with the range of the return type and expand to neighboring | ||||
// qualifiers (const, volatile and restrict). | // qualifiers (const, volatile and restrict). | ||||
SourceRange ReturnTypeRange = F.getReturnTypeSourceRange(); | SourceRange ReturnTypeRange = F.getReturnTypeSourceRange(); | ||||
if (ReturnTypeRange.isInvalid()) { | if (ReturnTypeRange.isInvalid()) { | ||||
// Happens if e.g. clang cannot resolve all includes and the return type is | // Happens if e.g. clang cannot resolve all includes and the return type is | ||||
// unknown. | // unknown. | ||||
diag(F.getLocation(), Message); | diag(F.getLocation(), Message); | ||||
return {}; | return {}; | ||||
} | } | ||||
// If the return type is a constrained 'auto' or 'decltype(auto)', we need to | |||||
// include the tokens after the concept. Unfortunately, the source range of an | |||||
// AutoTypeLoc, if it is constrained, does not include the 'auto' or | |||||
// 'decltype(auto)'. If the return type is a plain 'decltype(...)', the | |||||
// source range only contains the first 'decltype' token. | |||||
auto ATL = ReturnLoc.getAs<AutoTypeLoc>(); | |||||
if ((ATL && (ATL.isConstrained() || | |||||
ATL.getAutoKeyword() == AutoTypeKeyword::DecltypeAuto)) || | |||||
ReturnLoc.getAs<DecltypeTypeLoc>()) { | |||||
SourceLocation End = | |||||
expandIfMacroId(ReturnLoc.getSourceRange().getEnd(), SM); | |||||
SourceLocation BeginNameF = expandIfMacroId(F.getLocation(), SM); | |||||
// Extend the ReturnTypeRange until the last token before the function | |||||
// name. | |||||
std::pair<FileID, unsigned> Loc = SM.getDecomposedLoc(End); | |||||
StringRef File = SM.getBufferData(Loc.first); | |||||
const char *TokenBegin = File.data() + Loc.second; | |||||
aaron.ballman: Should we verify that `End` is valid before doing this pointer arithmetic with a value derived… | |||||
That sounded like a scary scenario! Fortunately, expandIfMacroId even expands source locations inside the scratch buffer correctly into a location in a real file. I added two tests for it and they seem to pass. Thank you for the hint! bernhardmgruber: That sounded like a scary scenario! Fortunately, `expandIfMacroId` even expands source… | |||||
Lexer Lexer(SM.getLocForStartOfFile(Loc.first), LangOpts, File.begin(), | |||||
TokenBegin, File.end()); | |||||
Token T; | |||||
SourceLocation LastTLoc = End; | |||||
while (!Lexer.LexFromRawLexer(T) && | |||||
SM.isBeforeInTranslationUnit(T.getLocation(), BeginNameF)) { | |||||
LastTLoc = T.getLocation(); | |||||
} | |||||
ReturnTypeRange.setEnd(LastTLoc); | |||||
Not Done ReplyInline ActionsIs there an easier way to get the token previous to the function name? bernhardmgruber: Is there an easier way to get the token previous to the function name? | |||||
Not Done ReplyInline ActionsThere isn't, but if you find you're missing source location information for something, you can also consider modifying Clang to add that source location information and mark this as a dependent patch. It's not clear to me whether that would be worth the effort for this patch or not, but my preference is to avoid these little hacks whenever possible. aaron.ballman: There isn't, but if you find you're missing source location information for something, you can… | |||||
Thank you for the hint! I considered this for the concept location in an AutoReturnType and the expression inside a decltype AutoReturnType. Unfortunately I could not wrap my head around what is going on in SemaType.cpp :/ bernhardmgruber: Thank you for the hint! I considered this for the concept location in an `AutoReturnType` and… | |||||
} | |||||
// If the return type has no local qualifiers, it's source range is accurate. | // If the return type has no local qualifiers, it's source range is accurate. | ||||
if (!hasAnyNestedLocalQualifiers(F.getReturnType())) | if (!hasAnyNestedLocalQualifiers(F.getReturnType())) | ||||
return ReturnTypeRange; | return ReturnTypeRange; | ||||
// Include qualifiers to the left and right of the return type. | // Include qualifiers to the left and right of the return type. | ||||
llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens = | llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens = | ||||
classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); | classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); | ||||
if (!MaybeTokens) | if (!MaybeTokens) | ||||
Show All 27 Lines | SourceRange UseTrailingReturnTypeCheck::findReturnTypeAndCVSourceRange( | ||||
assert(!ReturnTypeRange.getBegin().isMacroID() && | assert(!ReturnTypeRange.getBegin().isMacroID() && | ||||
"Return type source range begin must not be a macro"); | "Return type source range begin must not be a macro"); | ||||
assert(!ReturnTypeRange.getEnd().isMacroID() && | assert(!ReturnTypeRange.getEnd().isMacroID() && | ||||
"Return type source range end must not be a macro"); | "Return type source range end must not be a macro"); | ||||
return ReturnTypeRange; | return ReturnTypeRange; | ||||
} | } | ||||
bool UseTrailingReturnTypeCheck::keepSpecifiers( | void UseTrailingReturnTypeCheck::keepSpecifiers( | ||||
std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange, | std::string &ReturnType, std::string &Auto, SourceRange ReturnTypeCVRange, | ||||
const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx, | const FunctionDecl &F, const FriendDecl *Fr, const ASTContext &Ctx, | ||||
const SourceManager &SM, const LangOptions &LangOpts) { | const SourceManager &SM, const LangOptions &LangOpts) { | ||||
// Check if there are specifiers inside the return type. E.g. unsigned | // Check if there are specifiers inside the return type. E.g. unsigned | ||||
// inline int. | // inline int. | ||||
const auto *M = dyn_cast<CXXMethodDecl>(&F); | const auto *M = dyn_cast<CXXMethodDecl>(&F); | ||||
if (!F.isConstexpr() && !F.isInlineSpecified() && | if (!F.isConstexpr() && !F.isInlineSpecified() && | ||||
F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static && | F.getStorageClass() != SC_Extern && F.getStorageClass() != SC_Static && | ||||
!Fr && !(M && M->isVirtualAsWritten())) | !Fr && !(M && M->isVirtualAsWritten())) | ||||
return true; | return; | ||||
// Tokenize return type. If it contains macros which contain a mix of | // Tokenize return type. If it contains macros which contain a mix of | ||||
// qualifiers, specifiers and types, give up. | // qualifiers, specifiers and types, give up. | ||||
llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens = | llvm::Optional<SmallVector<ClassifiedToken, 8>> MaybeTokens = | ||||
classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); | classifyTokensBeforeFunctionName(F, Ctx, SM, LangOpts); | ||||
if (!MaybeTokens) | if (!MaybeTokens) | ||||
return false; | return; | ||||
// Find specifiers, remove them from the return type, add them to 'auto'. | // Find specifiers, remove them from the return type, add them to 'auto'. | ||||
unsigned int ReturnTypeBeginOffset = | unsigned int ReturnTypeBeginOffset = | ||||
SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second; | SM.getDecomposedLoc(ReturnTypeCVRange.getBegin()).second; | ||||
size_t InitialAutoLength = Auto.size(); | size_t InitialAutoLength = Auto.size(); | ||||
unsigned int DeletedChars = 0; | unsigned int DeletedChars = 0; | ||||
for (ClassifiedToken CT : *MaybeTokens) { | for (ClassifiedToken CT : *MaybeTokens) { | ||||
if (SM.isBeforeInTranslationUnit(CT.T.getLocation(), | if (SM.isBeforeInTranslationUnit(CT.T.getLocation(), | ||||
Show All 15 Lines | while (TOffsetInRT + TLengthWithWS < ReturnType.size() && | ||||
llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS])) | llvm::isSpace(ReturnType[TOffsetInRT + TLengthWithWS])) | ||||
TLengthWithWS++; | TLengthWithWS++; | ||||
std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS); | std::string Specifier = ReturnType.substr(TOffsetInRT, TLengthWithWS); | ||||
if (!llvm::isSpace(Specifier.back())) | if (!llvm::isSpace(Specifier.back())) | ||||
Specifier.push_back(' '); | Specifier.push_back(' '); | ||||
Auto.insert(Auto.size() - InitialAutoLength, Specifier); | Auto.insert(Auto.size() - InitialAutoLength, Specifier); | ||||
ReturnType.erase(TOffsetInRT, TLengthWithWS); | ReturnType.erase(TOffsetInRT, TLengthWithWS); | ||||
DeletedChars += TLengthWithWS; | DeletedChars += TLengthWithWS; | ||||
} | } | ||||
return true; | |||||
} | } | ||||
Non needed. See readability-redundant-control-flow. Eugene.Zelenko: Non needed. See readability-redundant-control-flow. | |||||
Thx! bernhardmgruber: Thx! | |||||
void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) { | void UseTrailingReturnTypeCheck::registerMatchers(MatchFinder *Finder) { | ||||
auto F = functionDecl(unless(anyOf(hasTrailingReturn(), returns(voidType()), | auto F = functionDecl( | ||||
returns(autoType()), cxxConversionDecl(), | unless(anyOf(hasTrailingReturn(), returns(voidType()), | ||||
cxxMethodDecl(isImplicit())))) | cxxConversionDecl(), cxxMethodDecl(isImplicit())))) | ||||
.bind("Func"); | .bind("Func"); | ||||
Finder->addMatcher(F, this); | Finder->addMatcher(F, this); | ||||
Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this); | Finder->addMatcher(friendDecl(hasDescendant(F)).bind("Friend"), this); | ||||
} | } | ||||
void UseTrailingReturnTypeCheck::registerPPCallbacks( | void UseTrailingReturnTypeCheck::registerPPCallbacks( | ||||
const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { | ||||
this->PP = PP; | this->PP = PP; | ||||
} | } | ||||
void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { | void UseTrailingReturnTypeCheck::check(const MatchFinder::MatchResult &Result) { | ||||
assert(PP && "Expected registerPPCallbacks() to have been called before so " | assert(PP && "Expected registerPPCallbacks() to have been called before so " | ||||
"preprocessor is available"); | "preprocessor is available"); | ||||
const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func"); | const auto *F = Result.Nodes.getNodeAs<FunctionDecl>("Func"); | ||||
const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend"); | const auto *Fr = Result.Nodes.getNodeAs<FriendDecl>("Friend"); | ||||
assert(F && "Matcher is expected to find only FunctionDecls"); | assert(F && "Matcher is expected to find only FunctionDecls"); | ||||
if (F->getLocation().isInvalid()) | if (F->getLocation().isInvalid()) | ||||
return; | return; | ||||
// Skip functions which return just 'auto'. | |||||
const auto *AT = F->getDeclaredReturnType()->getAs<AutoType>(); | |||||
if (AT != nullptr && !AT->isConstrained() && | |||||
AT->getKeyword() == AutoTypeKeyword::Auto && | |||||
!hasAnyNestedLocalQualifiers(F->getDeclaredReturnType())) | |||||
Why do we need to check that there aren't any nested local qualifiers? aaron.ballman: Why do we need to check that there aren't any nested local qualifiers? | |||||
Because I would like the check to rewrite e.g. const auto f(); into auto f() -> const auto;. If I omit the check for nested local qualifiers, then those kind of declarations would be skipped. bernhardmgruber: Because I would like the check to rewrite e.g. `const auto f();` into `auto f() -> const auto;`. | |||||
I don't think I understand why that's desirable though? What is it about the qualifier that makes it worthwhile to repeat the type like that? aaron.ballman: > Because I would like the check to rewrite e.g. const auto f(); into auto f() -> const auto;. | |||||
I'm still wondering about this. aaron.ballman: I'm still wondering about this. | |||||
Thank you for insisting on that peculiarity! The choice is stylistically motivated to align function names: auto f() -> int; auto g() -> std::vector<float>; auto& h(); const auto k(); decltype(auto) l(); vs. auto f() -> int; auto g() -> std::vector<float>; auto h() -> auto&; auto k() -> const auto; auto l() -> decltype(auto); But judging from your response, this might be a surprise to users. Would you prefer having an option to enable/disable this behavior? bernhardmgruber: Thank you for insisting on that peculiarity! The choice is stylistically motivated to align… | |||||
Not Done ReplyInline Actions
Maybe it will be, maybe it won't. :-D The reason I was surprised was because it feels like a formatting related choice rather than a modernization related choice. However, I've always struggled to understand the utility of this check (it's one I disable in every .clang-tidy configuration file I can), so my reasoning may be flawed. I feel like the goal of this check isn't to format code nicely, it's to modernize to using a trailing return type where that adds clarity. But I don't think auto& func() changing into auto func() -> auto& adds clarity (I think it removes clarity because the second signature is strictly more complex than the first), and similar for qualifiers. However, I think the exact same thing about int func() changing into auto func() -> int. Given that we document this function to rewrite all functions to a trailing return type signature, I guess the behavior you've proposed here is consistent with that goal and so I'm fine with it. aaron.ballman: > But judging from your response, this might be a surprise to users. Would you prefer having an… | |||||
I am sorry to hear that, but I have heard this from many other people as well. I am sometimes questioning myself whether it was a mistake to put this check into clang-tidy and annoy a lot of people. It might have been better as a standalone tool.
I like that thinking! I started with trailing return types as a stylistic choice, but I soon realized that it indeed can add clarity by shifting away complicated return types to the end of a line where they bother me less.
With regard to clarity, you are right. And I noted down now that I shall add an option to prevent some of these rewrites. Thank you for the feedback! bernhardmgruber: > However, I've always struggled to understand the utility of this check (it's one I disable in… | |||||
Not Done ReplyInline Actions
FWIW, I don't question whether it was a mistake to add it. I just figure it wasn't targeted at me, and that's fine too. I can see the utility for people who want that kind of declaration style. aaron.ballman: > I am sorry to hear that, but I have heard this from many other people as well. I am sometimes… | |||||
return; | |||||
// TODO: implement those | // TODO: implement those | ||||
if (F->getDeclaredReturnType()->isFunctionPointerType() || | if (F->getDeclaredReturnType()->isFunctionPointerType() || | ||||
F->getDeclaredReturnType()->isMemberFunctionPointerType() || | F->getDeclaredReturnType()->isMemberFunctionPointerType() || | ||||
F->getDeclaredReturnType()->isMemberPointerType() || | F->getDeclaredReturnType()->isMemberPointerType()) { | ||||
F->getDeclaredReturnType()->getAs<DecltypeType>() != nullptr) { | |||||
diag(F->getLocation(), Message); | diag(F->getLocation(), Message); | ||||
return; | return; | ||||
} | } | ||||
const ASTContext &Ctx = *Result.Context; | const ASTContext &Ctx = *Result.Context; | ||||
const SourceManager &SM = *Result.SourceManager; | const SourceManager &SM = *Result.SourceManager; | ||||
const LangOptions &LangOpts = getLangOpts(); | const LangOptions &LangOpts = getLangOpts(); | ||||
Show All 17 Lines | if (InsertionLoc.isInvalid()) { | ||||
diag(F->getLocation(), Message); | diag(F->getLocation(), Message); | ||||
return; | return; | ||||
} | } | ||||
// Using the declared return type via F->getDeclaredReturnType().getAsString() | // Using the declared return type via F->getDeclaredReturnType().getAsString() | ||||
// discards user formatting and order of const, volatile, type, whitespace, | // discards user formatting and order of const, volatile, type, whitespace, | ||||
// space before & ... . | // space before & ... . | ||||
SourceRange ReturnTypeCVRange = | SourceRange ReturnTypeCVRange = | ||||
findReturnTypeAndCVSourceRange(*F, Ctx, SM, LangOpts); | findReturnTypeAndCVSourceRange(*F, FTL.getReturnLoc(), Ctx, SM, LangOpts); | ||||
if (ReturnTypeCVRange.isInvalid()) | if (ReturnTypeCVRange.isInvalid()) | ||||
return; | return; | ||||
// Check if unqualified names in the return type conflict with other entities | // Check if unqualified names in the return type conflict with other entities | ||||
// after the rewrite. | // after the rewrite. | ||||
// FIXME: this could be done better, by performing a lookup of all | // FIXME: this could be done better, by performing a lookup of all | ||||
// unqualified names in the return type in the scope of the function. If the | // unqualified names in the return type in the scope of the function. If the | ||||
// lookup finds a different entity than the original entity identified by the | // lookup finds a different entity than the original entity identified by the | ||||
Show All 33 Lines |
Should we verify that End is valid before doing this pointer arithmetic with a value derived from it? For instance, what if End points into the scratch buffer because it relies on a macro defined on the command line -- will the logic still work?