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:
Douglas Gregor 2017-07-05 20:20:14 +00:00
parent d968f6f423
commit 00fa10b43f
7 changed files with 165 additions and 12 deletions

View File

@ -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<

View File

@ -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);

View File

@ -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) {

View File

@ -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.

View File

@ -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();
}

View File

@ -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; };

View File

@ -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> >