forked from OSchip/llvm-project
Customize the SFINAE diagnostics for enable_if to provide the failed condition.
When enable_if disables a particular overload resolution candidate, rummage through the enable_if condition to find the specific condition that caused the failure. For example, if we have something like: template< typename Iter, typename = std::enable_if_t<Random_access_iterator<Iter> && Comparable<Iterator_value_type<Iter>>>> void mysort(Iter first, Iter last) {} and we call "mysort" with "std::list<int>" iterators, we'll get a diagnostic saying that the "Random_access_iterator<Iter>" requirement failed. If we call "mysort" with "std::vector<something_not_comparable>", we'll get a diagnostic saying that the "Comparable<...>" requirement failed. llvm-svn: 307196
This commit is contained in:
parent
d968f6f423
commit
00fa10b43f
|
@ -3518,6 +3518,8 @@ def note_ovl_candidate_substitution_failure : Note<
|
|||
"candidate template ignored: substitution failure%0%1">;
|
||||
def note_ovl_candidate_disabled_by_enable_if : Note<
|
||||
"candidate template ignored: disabled by %0%1">;
|
||||
def note_ovl_candidate_disabled_by_requirement : Note<
|
||||
"candidate template ignored: requirement '%0' was not satisfied%1">;
|
||||
def note_ovl_candidate_has_pass_object_size_params: Note<
|
||||
"candidate address cannot be taken because parameter %0 has "
|
||||
"pass_object_size attribute">;
|
||||
|
@ -4431,6 +4433,9 @@ def err_typename_nested_not_found : Error<"no type named %0 in %1">;
|
|||
def err_typename_nested_not_found_enable_if : Error<
|
||||
"no type named 'type' in %0; 'enable_if' cannot be used to disable "
|
||||
"this declaration">;
|
||||
def err_typename_nested_not_found_requirement : Error<
|
||||
"failed requirement '%0'; 'enable_if' cannot be used to disable this "
|
||||
"declaration">;
|
||||
def err_typename_nested_not_type : Error<
|
||||
"typename specifier refers to non-type member %0 in %1">;
|
||||
def note_typename_refers_here : Note<
|
||||
|
|
|
@ -329,6 +329,15 @@ public:
|
|||
|
||||
bool hasStorage() const { return DiagStorage != nullptr; }
|
||||
|
||||
/// Retrieve the string argument at the given index.
|
||||
StringRef getStringArg(unsigned I) {
|
||||
assert(DiagStorage && "No diagnostic storage?");
|
||||
assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
|
||||
assert(DiagStorage->DiagArgumentsKind[I]
|
||||
== DiagnosticsEngine::ak_std_string && "Not a string arg");
|
||||
return DiagStorage->DiagArgumentsStr[I];
|
||||
}
|
||||
|
||||
friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD,
|
||||
unsigned I) {
|
||||
PD.AddTaggedVal(I, DiagnosticsEngine::ak_uint);
|
||||
|
|
|
@ -88,6 +88,12 @@ public:
|
|||
HasSFINAEDiagnostic = false;
|
||||
}
|
||||
|
||||
/// Peek at the SFINAE diagnostic.
|
||||
const PartialDiagnosticAt &peekSFINAEDiagnostic() const {
|
||||
assert(HasSFINAEDiagnostic);
|
||||
return SuppressedDiagnostics.front();
|
||||
}
|
||||
|
||||
/// \brief Provide a new template argument list that contains the
|
||||
/// results of template argument deduction.
|
||||
void reset(TemplateArgumentList *NewDeduced) {
|
||||
|
|
|
@ -9830,6 +9830,15 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
|
|||
return;
|
||||
}
|
||||
|
||||
// We found a specific requirement that disabled the enable_if.
|
||||
if (PDiag && PDiag->second.getDiagID() ==
|
||||
diag::err_typename_nested_not_found_requirement) {
|
||||
S.Diag(Templated->getLocation(),
|
||||
diag::note_ovl_candidate_disabled_by_requirement)
|
||||
<< PDiag->second.getStringArg(0) << TemplateArgString;
|
||||
return;
|
||||
}
|
||||
|
||||
// Format the SFINAE diagnostic into the argument string.
|
||||
// FIXME: Add a general mechanism to include a PartialDiagnostic *'s
|
||||
// formatted message in another diagnostic.
|
||||
|
|
|
@ -2806,6 +2806,67 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
|
|||
llvm_unreachable("unexpected BuiltinTemplateDecl!");
|
||||
}
|
||||
|
||||
/// Determine whether this alias template is "enable_if_t".
|
||||
static bool isEnableIfAliasTemplate(TypeAliasTemplateDecl *AliasTemplate) {
|
||||
return AliasTemplate->getName().equals("enable_if_t");
|
||||
}
|
||||
|
||||
/// Collect all of the separable terms in the given condition, which
|
||||
/// might be a conjunction.
|
||||
///
|
||||
/// FIXME: The right answer is to convert the logical expression into
|
||||
/// disjunctive normal form, so we can find the first failed term
|
||||
/// within each possible clause.
|
||||
static void collectConjunctionTerms(Expr *Clause,
|
||||
SmallVectorImpl<Expr *> &Terms) {
|
||||
if (auto BinOp = dyn_cast<BinaryOperator>(Clause->IgnoreParenImpCasts())) {
|
||||
if (BinOp->getOpcode() == BO_LAnd) {
|
||||
collectConjunctionTerms(BinOp->getLHS(), Terms);
|
||||
collectConjunctionTerms(BinOp->getRHS(), Terms);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Terms.push_back(Clause);
|
||||
}
|
||||
|
||||
/// Find the failed subexpression within enable_if, and describe it
|
||||
/// with a string.
|
||||
static std::pair<Expr *, std::string>
|
||||
findFailedEnableIfCondition(Sema &S, Expr *Cond) {
|
||||
// Separate out all of the terms in a conjunction.
|
||||
SmallVector<Expr *, 4> Terms;
|
||||
collectConjunctionTerms(Cond, Terms);
|
||||
|
||||
// Determine which term failed.
|
||||
Expr *FailedCond = nullptr;
|
||||
for (Expr *Term : Terms) {
|
||||
// The initialization of the parameter from the argument is
|
||||
// a constant-evaluated context.
|
||||
EnterExpressionEvaluationContext ConstantEvaluated(
|
||||
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
|
||||
|
||||
bool Succeeded;
|
||||
if (Term->EvaluateAsBooleanCondition(Succeeded, S.Context) &&
|
||||
!Succeeded) {
|
||||
FailedCond = Term->IgnoreParenImpCasts();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!FailedCond)
|
||||
FailedCond = Cond->IgnoreParenImpCasts();
|
||||
|
||||
std::string Description;
|
||||
{
|
||||
llvm::raw_string_ostream Out(Description);
|
||||
FailedCond->printPretty(Out, nullptr,
|
||||
PrintingPolicy(S.Context.getLangOpts()));
|
||||
}
|
||||
return { FailedCond, Description };
|
||||
}
|
||||
|
||||
QualType Sema::CheckTemplateIdType(TemplateName Name,
|
||||
SourceLocation TemplateLoc,
|
||||
TemplateArgumentListInfo &TemplateArgs) {
|
||||
|
@ -2852,12 +2913,12 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
|
|||
if (Pattern->isInvalidDecl())
|
||||
return QualType();
|
||||
|
||||
TemplateArgumentList TemplateArgs(TemplateArgumentList::OnStack,
|
||||
Converted);
|
||||
TemplateArgumentList StackTemplateArgs(TemplateArgumentList::OnStack,
|
||||
Converted);
|
||||
|
||||
// Only substitute for the innermost template argument list.
|
||||
MultiLevelTemplateArgumentList TemplateArgLists;
|
||||
TemplateArgLists.addOuterTemplateArguments(&TemplateArgs);
|
||||
TemplateArgLists.addOuterTemplateArguments(&StackTemplateArgs);
|
||||
unsigned Depth = AliasTemplate->getTemplateParameters()->getDepth();
|
||||
for (unsigned I = 0; I < Depth; ++I)
|
||||
TemplateArgLists.addOuterTemplateArguments(None);
|
||||
|
@ -2870,8 +2931,42 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
|
|||
CanonType = SubstType(Pattern->getUnderlyingType(),
|
||||
TemplateArgLists, AliasTemplate->getLocation(),
|
||||
AliasTemplate->getDeclName());
|
||||
if (CanonType.isNull())
|
||||
if (CanonType.isNull()) {
|
||||
// If this was enable_if and we failed to find the nested type
|
||||
// within enable_if in a SFINAE context, dig out the specific
|
||||
// enable_if condition that failed and present that instead.
|
||||
if (isEnableIfAliasTemplate(AliasTemplate)) {
|
||||
if (auto DeductionInfo = isSFINAEContext()) {
|
||||
if (*DeductionInfo &&
|
||||
(*DeductionInfo)->hasSFINAEDiagnostic() &&
|
||||
(*DeductionInfo)->peekSFINAEDiagnostic().second.getDiagID() ==
|
||||
diag::err_typename_nested_not_found_enable_if &&
|
||||
TemplateArgs[0].getArgument().getKind()
|
||||
== TemplateArgument::Expression) {
|
||||
Expr *FailedCond;
|
||||
std::string FailedDescription;
|
||||
std::tie(FailedCond, FailedDescription) =
|
||||
findFailedEnableIfCondition(
|
||||
*this, TemplateArgs[0].getSourceExpression());
|
||||
|
||||
// Remove the old SFINAE diagnostic.
|
||||
PartialDiagnosticAt OldDiag =
|
||||
{SourceLocation(), PartialDiagnostic::NullDiagnostic()};
|
||||
(*DeductionInfo)->takeSFINAEDiagnostic(OldDiag);
|
||||
|
||||
// Add a new SFINAE diagnostic specifying which condition
|
||||
// failed.
|
||||
(*DeductionInfo)->addSFINAEDiagnostic(
|
||||
OldDiag.first,
|
||||
PDiag(diag::err_typename_nested_not_found_requirement)
|
||||
<< FailedDescription
|
||||
<< FailedCond->getSourceRange());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QualType();
|
||||
}
|
||||
} else if (Name.isDependent() ||
|
||||
TemplateSpecializationType::anyDependentTemplateArguments(
|
||||
TemplateArgs, InstantiationDependent)) {
|
||||
|
@ -9290,7 +9385,7 @@ Sema::ActOnTypenameType(Scope *S,
|
|||
/// Determine whether this failed name lookup should be treated as being
|
||||
/// disabled by a usage of std::enable_if.
|
||||
static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
|
||||
SourceRange &CondRange) {
|
||||
SourceRange &CondRange, Expr *&Cond) {
|
||||
// We must be looking for a ::type...
|
||||
if (!II.isStr("type"))
|
||||
return false;
|
||||
|
@ -9320,6 +9415,19 @@ static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
|
|||
|
||||
// Assume the first template argument is the condition.
|
||||
CondRange = EnableIfTSTLoc.getArgLoc(0).getSourceRange();
|
||||
|
||||
// Dig out the condition.
|
||||
Cond = nullptr;
|
||||
if (EnableIfTSTLoc.getArgLoc(0).getArgument().getKind()
|
||||
!= TemplateArgument::Expression)
|
||||
return true;
|
||||
|
||||
Cond = EnableIfTSTLoc.getArgLoc(0).getSourceExpression();
|
||||
|
||||
// Ignore Boolean literals; they add no value.
|
||||
if (isa<CXXBoolLiteralExpr>(Cond->IgnoreParenCasts()))
|
||||
Cond = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -9363,9 +9471,25 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
|
|||
// If we're looking up 'type' within a template named 'enable_if', produce
|
||||
// a more specific diagnostic.
|
||||
SourceRange CondRange;
|
||||
if (isEnableIf(QualifierLoc, II, CondRange)) {
|
||||
Expr *Cond = nullptr;
|
||||
if (isEnableIf(QualifierLoc, II, CondRange, Cond)) {
|
||||
// If we have a condition, narrow it down to the specific failed
|
||||
// condition.
|
||||
if (Cond) {
|
||||
Expr *FailedCond;
|
||||
std::string FailedDescription;
|
||||
std::tie(FailedCond, FailedDescription) =
|
||||
findFailedEnableIfCondition(*this, Cond);
|
||||
|
||||
Diag(FailedCond->getExprLoc(),
|
||||
diag::err_typename_nested_not_found_requirement)
|
||||
<< FailedDescription
|
||||
<< FailedCond->getSourceRange();
|
||||
return QualType();
|
||||
}
|
||||
|
||||
Diag(CondRange.getBegin(), diag::err_typename_nested_not_found_enable_if)
|
||||
<< Ctx << CondRange;
|
||||
<< Ctx << CondRange;
|
||||
return QualType();
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ namespace Unevaluated {
|
|||
static constexpr bool f() { return sizeof(T) < U::size; }
|
||||
|
||||
template<typename U>
|
||||
static typename enable_if<f<U>(), void>::type g() {} // expected-note {{disabled by 'enable_if'}}
|
||||
static typename enable_if<f<U>(), void>::type g() {} // expected-note {{requirement 'f<Unevaluated::PR13423::U>()' was not satisfied}}
|
||||
};
|
||||
|
||||
struct U { static constexpr int size = 2; };
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace boost {
|
|||
template<bool, typename = void> struct enable_if {};
|
||||
template<typename T> struct enable_if<true, T> { typedef T type; };
|
||||
}
|
||||
template<typename T> typename boost::enable_if<sizeof(T) == 4, int>::type if_size_4(); // expected-note{{candidate template ignored: disabled by 'enable_if' [with T = char]}}
|
||||
template<typename T> typename boost::enable_if<sizeof(T) == 4, int>::type if_size_4(); // expected-note{{candidate template ignored: requirement 'sizeof(char) == 4' was not satisfied [with T = char]}}
|
||||
int k = if_size_4<char>(); // expected-error{{no matching function}}
|
||||
|
||||
namespace llvm {
|
||||
|
@ -61,7 +61,7 @@ void test_if_int() {
|
|||
}
|
||||
|
||||
template<typename T> struct NonTemplateFunction {
|
||||
typename boost::enable_if<sizeof(T) == 4, int>::type f(); // expected-error{{no type named 'type' in 'boost::enable_if<false, int>'; 'enable_if' cannot be used to disable this declaration}}
|
||||
typename boost::enable_if<sizeof(T) == 4, int>::type f(); // expected-error{{failed requirement 'sizeof(char) == 4'; 'enable_if' cannot be used to disable this declaration}}
|
||||
};
|
||||
NonTemplateFunction<char> NTFC; // expected-note{{here}}
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace PR15673 {
|
|||
#if __cplusplus <= 199711L
|
||||
// expected-warning@-2 {{default template arguments for a function template are a C++11 extension}}
|
||||
#endif
|
||||
// expected-note@-4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
|
||||
// expected-note@+1 {{candidate template ignored: requirement 'a_trait<int>::value' was not satisfied [with T = int]}}
|
||||
void foo() {}
|
||||
void bar() { foo<int>(); } // expected-error {{no matching function for call to 'foo'}}
|
||||
|
||||
|
@ -128,7 +128,7 @@ namespace PR15673 {
|
|||
#if __cplusplus <= 199711L
|
||||
// expected-warning@-2 {{alias declarations are a C++11 extension}}
|
||||
#endif
|
||||
// expected-note@-4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
|
||||
// expected-note@+7 {{candidate template ignored: requirement 'some_trait<int>::value' was not satisfied [with T = int]}}
|
||||
|
||||
template<typename T,
|
||||
typename Requires = unicorns<T> >
|
||||
|
|
Loading…
Reference in New Issue