r307196 - Customize the SFINAE diagnostics for enable_if to provide the failed condition.
Douglas Gregor via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 5 13:20:14 PDT 2017
Author: dgregor
Date: Wed Jul 5 13:20:14 2017
New Revision: 307196
URL: http://llvm.org/viewvc/llvm-project?rev=307196&view=rev
Log:
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.
Modified:
cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
cfe/trunk/include/clang/Basic/PartialDiagnostic.h
cfe/trunk/include/clang/Sema/TemplateDeduction.h
cfe/trunk/lib/Sema/SemaOverload.cpp
cfe/trunk/lib/Sema/SemaTemplate.cpp
cfe/trunk/test/SemaTemplate/constexpr-instantiate.cpp
cfe/trunk/test/SemaTemplate/overload-candidates.cpp
Modified: cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticSemaKinds.td Wed Jul 5 13:20:14 2017
@@ -3518,6 +3518,8 @@ def note_ovl_candidate_substitution_fail
"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 : Erro
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<
Modified: cfe/trunk/include/clang/Basic/PartialDiagnostic.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/PartialDiagnostic.h?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/include/clang/Basic/PartialDiagnostic.h (original)
+++ cfe/trunk/include/clang/Basic/PartialDiagnostic.h Wed Jul 5 13:20:14 2017
@@ -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);
Modified: cfe/trunk/include/clang/Sema/TemplateDeduction.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Sema/TemplateDeduction.h?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/include/clang/Sema/TemplateDeduction.h (original)
+++ cfe/trunk/include/clang/Sema/TemplateDeduction.h Wed Jul 5 13:20:14 2017
@@ -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) {
Modified: cfe/trunk/lib/Sema/SemaOverload.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaOverload.cpp?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaOverload.cpp (original)
+++ cfe/trunk/lib/Sema/SemaOverload.cpp Wed Jul 5 13:20:14 2017
@@ -9830,6 +9830,15 @@ static void DiagnoseBadDeduction(Sema &S
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.
Modified: cfe/trunk/lib/Sema/SemaTemplate.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Sema/SemaTemplate.cpp?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/lib/Sema/SemaTemplate.cpp (original)
+++ cfe/trunk/lib/Sema/SemaTemplate.cpp Wed Jul 5 13:20:14 2017
@@ -2806,6 +2806,67 @@ checkBuiltinTemplateIdType(Sema &SemaRef
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(Templ
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(Templ
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(NestedNameSpecifi
// 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(ElaboratedTypeKe
// 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();
}
Modified: cfe/trunk/test/SemaTemplate/constexpr-instantiate.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaTemplate/constexpr-instantiate.cpp?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/test/SemaTemplate/constexpr-instantiate.cpp (original)
+++ cfe/trunk/test/SemaTemplate/constexpr-instantiate.cpp Wed Jul 5 13:20:14 2017
@@ -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; };
Modified: cfe/trunk/test/SemaTemplate/overload-candidates.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/test/SemaTemplate/overload-candidates.cpp?rev=307196&r1=307195&r2=307196&view=diff
==============================================================================
--- cfe/trunk/test/SemaTemplate/overload-candidates.cpp (original)
+++ cfe/trunk/test/SemaTemplate/overload-candidates.cpp Wed Jul 5 13:20:14 2017
@@ -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 at -2 {{default template arguments for a function template are a C++11 extension}}
#endif
- // expected-note at -4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
+ // expected-note at +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 at -2 {{alias declarations are a C++11 extension}}
#endif
- // expected-note at -4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
+ // expected-note at +7 {{candidate template ignored: requirement 'some_trait<int>::value' was not satisfied [with T = int]}}
template<typename T,
typename Requires = unicorns<T> >
More information about the cfe-commits
mailing list