Skip to content

Commit

Permalink
Customize the SFINAE diagnostics for enable_if to provide the failed …
Browse files Browse the repository at this point in the history
…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.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@307196 91177308-0d34-0410-b5e6-96231b3b80d8
  • Loading branch information
DougGregor committed Jul 5, 2017
1 parent 29e21c3 commit d93c41c
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 12 deletions.
5 changes: 5 additions & 0 deletions include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Expand Down Expand Up @@ -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<
Expand Down
9 changes: 9 additions & 0 deletions include/clang/Basic/PartialDiagnostic.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,15 @@ class PartialDiagnostic {

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);
Expand Down
6 changes: 6 additions & 0 deletions include/clang/Sema/TemplateDeduction.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ class TemplateDeductionInfo {
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) {
Expand Down
9 changes: 9 additions & 0 deletions lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
138 changes: 131 additions & 7 deletions lib/Sema/SemaTemplate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

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

Expand Down
2 changes: 1 addition & 1 deletion test/SemaTemplate/constexpr-instantiate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; };
Expand Down
8 changes: 4 additions & 4 deletions test/SemaTemplate/overload-candidates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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}}

Expand Down Expand Up @@ -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'}}

Expand Down Expand Up @@ -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> >
Expand Down

0 comments on commit d93c41c

Please sign in to comment.