//===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===// | |
// | |
// The LLVM Compiler Infrastructure | |
// | |
// This file is distributed under the University of Illinois Open Source | |
// License. See LICENSE.TXT for details. | |
// | |
//===----------------------------------------------------------------------===// | |
#include "ForwardingReferenceOverloadCheck.h" | |
#include "clang/AST/ASTContext.h" | |
#include "clang/ASTMatchers/ASTMatchFinder.h" | |
#include <algorithm> | |
using namespace clang::ast_matchers; | |
namespace clang { | |
namespace tidy { | |
namespace misc { | |
namespace { | |
// Check if the given type is related to std::enable_if. | |
AST_MATCHER(QualType, isEnableIf) { | |
auto CheckTemplate = [](const TemplateSpecializationType *Spec) { | |
if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) { | |
return false; | |
} | |
const NamedDecl *TypeDecl = | |
Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl(); | |
return TypeDecl->isInStdNamespace() && | |
(TypeDecl->getName().equals("enable_if") || | |
TypeDecl->getName().equals("enable_if_t")); | |
}; | |
const Type *BaseType = Node.getTypePtr(); | |
// Case: pointer or reference to enable_if. | |
while (BaseType->isPointerType() || BaseType->isReferenceType()) { | |
BaseType = BaseType->getPointeeType().getTypePtr(); | |
} | |
// Case: type parameter dependent (enable_if<is_integral<T>>). | |
if (const auto *Dependent = BaseType->getAs<DependentNameType>()) { | |
BaseType = Dependent->getQualifier()->getAsType(); | |
} | |
if (!BaseType) | |
return false; | |
if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) { | |
return true; // Case: enable_if_t< >. | |
} else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) { | |
if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) { | |
if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) { | |
return true; // Case: enable_if< >::type. | |
} | |
} | |
} | |
return false; | |
} | |
AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, | |
clang::ast_matchers::internal::Matcher<QualType>, TypeMatcher) { | |
return Node.hasDefaultArgument() && | |
TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder); | |
} | |
} | |
void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) { | |
// Forwarding references require C++11 or later. | |
if (!getLangOpts().CPlusPlus11) | |
return; | |
auto ForwardingRefParm = | |
parmVarDecl( | |
hasType(qualType(rValueReferenceType(), | |
references(templateTypeParmType(hasDeclaration( | |
templateTypeParmDecl().bind("type-parm-decl")))), | |
unless(references(isConstQualified()))))) | |
.bind("parm-var"); | |
DeclarationMatcher findOverload = | |
cxxConstructorDecl( | |
hasParameter(0, ForwardingRefParm), | |
unless(hasAnyParameter( | |
// No warning: enable_if as constructor parameter. | |
parmVarDecl(hasType(isEnableIf())))), | |
unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl( | |
// No warning: enable_if as type parameter. | |
hasDefaultArgument(isEnableIf()))))))) | |
.bind("ctor"); | |
Finder->addMatcher(findOverload, this); | |
} | |
void ForwardingReferenceOverloadCheck::check( | |
const MatchFinder::MatchResult &Result) { | |
const auto *ParmVar = Result.Nodes.getNodeAs<ParmVarDecl>("parm-var"); | |
const auto *TypeParmDecl = | |
Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl"); | |
// Get the FunctionDecl and FunctionTemplateDecl containing the function | |
// parameter. | |
const auto *FuncForParam = dyn_cast<FunctionDecl>(ParmVar->getDeclContext()); | |
if (!FuncForParam) | |
return; | |
const FunctionTemplateDecl *FuncTemplate = | |
FuncForParam->getDescribedFunctionTemplate(); | |
if (!FuncTemplate) | |
return; | |
// Check that the template type parameter belongs to the same function | |
// template as the function parameter of that type. (This implies that type | |
// deduction will happen on the type.) | |
const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); | |
if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end()) | |
return; | |
// Every parameter after the first must have a default value. | |
const auto *Ctor = Result.Nodes.getNodeAs<CXXConstructorDecl>("ctor"); | |
for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) { | |
if (!(*Iter)->hasDefaultArg()) | |
return; | |
} | |
bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false, | |
DisabledMove = false; | |
for (const auto *OtherCtor : Ctor->getParent()->ctors()) { | |
if (OtherCtor->isCopyOrMoveConstructor()) { | |
if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private) | |
(OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true; | |
else | |
(OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true; | |
} | |
} | |
bool Copy = !DisabledCopy || EnabledCopy, Move = !DisabledMove || EnabledMove; | |
if (!Copy && !Move) | |
return; | |
diag(Ctor->getLocation(), | |
"constructor accepting a forwarding reference can " | |
"hide the %select{copy|move|copy and move}0 constructor%s1") | |
<< (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move; | |
for (const auto *OtherCtor : Ctor->getParent()->ctors()) { | |
if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() && | |
OtherCtor->getAccess() != AS_private) { | |
diag(OtherCtor->getLocation(), | |
"%select{copy|move}0 constructor declared here", DiagnosticIDs::Note) | |
<< OtherCtor->isMoveConstructor(); | |
} | |
} | |
} | |
} // namespace misc | |
} // namespace tidy | |
} // namespace clang |