Skip to content
This repository was archived by the owner on Nov 1, 2021. It is now read-only.

Commit d93c41c

Browse files
committed
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. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@307196 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 29e21c3 commit d93c41c

7 files changed

+165
-12
lines changed

include/clang/Basic/DiagnosticSemaKinds.td

+5
Original file line numberDiff line numberDiff line change
@@ -3518,6 +3518,8 @@ def note_ovl_candidate_substitution_failure : Note<
35183518
"candidate template ignored: substitution failure%0%1">;
35193519
def note_ovl_candidate_disabled_by_enable_if : Note<
35203520
"candidate template ignored: disabled by %0%1">;
3521+
def note_ovl_candidate_disabled_by_requirement : Note<
3522+
"candidate template ignored: requirement '%0' was not satisfied%1">;
35213523
def note_ovl_candidate_has_pass_object_size_params: Note<
35223524
"candidate address cannot be taken because parameter %0 has "
35233525
"pass_object_size attribute">;
@@ -4431,6 +4433,9 @@ def err_typename_nested_not_found : Error<"no type named %0 in %1">;
44314433
def err_typename_nested_not_found_enable_if : Error<
44324434
"no type named 'type' in %0; 'enable_if' cannot be used to disable "
44334435
"this declaration">;
4436+
def err_typename_nested_not_found_requirement : Error<
4437+
"failed requirement '%0'; 'enable_if' cannot be used to disable this "
4438+
"declaration">;
44344439
def err_typename_nested_not_type : Error<
44354440
"typename specifier refers to non-type member %0 in %1">;
44364441
def note_typename_refers_here : Note<

include/clang/Basic/PartialDiagnostic.h

+9
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,15 @@ class PartialDiagnostic {
329329

330330
bool hasStorage() const { return DiagStorage != nullptr; }
331331

332+
/// Retrieve the string argument at the given index.
333+
StringRef getStringArg(unsigned I) {
334+
assert(DiagStorage && "No diagnostic storage?");
335+
assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
336+
assert(DiagStorage->DiagArgumentsKind[I]
337+
== DiagnosticsEngine::ak_std_string && "Not a string arg");
338+
return DiagStorage->DiagArgumentsStr[I];
339+
}
340+
332341
friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD,
333342
unsigned I) {
334343
PD.AddTaggedVal(I, DiagnosticsEngine::ak_uint);

include/clang/Sema/TemplateDeduction.h

+6
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ class TemplateDeductionInfo {
8888
HasSFINAEDiagnostic = false;
8989
}
9090

91+
/// Peek at the SFINAE diagnostic.
92+
const PartialDiagnosticAt &peekSFINAEDiagnostic() const {
93+
assert(HasSFINAEDiagnostic);
94+
return SuppressedDiagnostics.front();
95+
}
96+
9197
/// \brief Provide a new template argument list that contains the
9298
/// results of template argument deduction.
9399
void reset(TemplateArgumentList *NewDeduced) {

lib/Sema/SemaOverload.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -9830,6 +9830,15 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
98309830
return;
98319831
}
98329832

9833+
// We found a specific requirement that disabled the enable_if.
9834+
if (PDiag && PDiag->second.getDiagID() ==
9835+
diag::err_typename_nested_not_found_requirement) {
9836+
S.Diag(Templated->getLocation(),
9837+
diag::note_ovl_candidate_disabled_by_requirement)
9838+
<< PDiag->second.getStringArg(0) << TemplateArgString;
9839+
return;
9840+
}
9841+
98339842
// Format the SFINAE diagnostic into the argument string.
98349843
// FIXME: Add a general mechanism to include a PartialDiagnostic *'s
98359844
// formatted message in another diagnostic.

lib/Sema/SemaTemplate.cpp

+131-7
Original file line numberDiff line numberDiff line change
@@ -2806,6 +2806,67 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
28062806
llvm_unreachable("unexpected BuiltinTemplateDecl!");
28072807
}
28082808

2809+
/// Determine whether this alias template is "enable_if_t".
2810+
static bool isEnableIfAliasTemplate(TypeAliasTemplateDecl *AliasTemplate) {
2811+
return AliasTemplate->getName().equals("enable_if_t");
2812+
}
2813+
2814+
/// Collect all of the separable terms in the given condition, which
2815+
/// might be a conjunction.
2816+
///
2817+
/// FIXME: The right answer is to convert the logical expression into
2818+
/// disjunctive normal form, so we can find the first failed term
2819+
/// within each possible clause.
2820+
static void collectConjunctionTerms(Expr *Clause,
2821+
SmallVectorImpl<Expr *> &Terms) {
2822+
if (auto BinOp = dyn_cast<BinaryOperator>(Clause->IgnoreParenImpCasts())) {
2823+
if (BinOp->getOpcode() == BO_LAnd) {
2824+
collectConjunctionTerms(BinOp->getLHS(), Terms);
2825+
collectConjunctionTerms(BinOp->getRHS(), Terms);
2826+
}
2827+
2828+
return;
2829+
}
2830+
2831+
Terms.push_back(Clause);
2832+
}
2833+
2834+
/// Find the failed subexpression within enable_if, and describe it
2835+
/// with a string.
2836+
static std::pair<Expr *, std::string>
2837+
findFailedEnableIfCondition(Sema &S, Expr *Cond) {
2838+
// Separate out all of the terms in a conjunction.
2839+
SmallVector<Expr *, 4> Terms;
2840+
collectConjunctionTerms(Cond, Terms);
2841+
2842+
// Determine which term failed.
2843+
Expr *FailedCond = nullptr;
2844+
for (Expr *Term : Terms) {
2845+
// The initialization of the parameter from the argument is
2846+
// a constant-evaluated context.
2847+
EnterExpressionEvaluationContext ConstantEvaluated(
2848+
S, Sema::ExpressionEvaluationContext::ConstantEvaluated);
2849+
2850+
bool Succeeded;
2851+
if (Term->EvaluateAsBooleanCondition(Succeeded, S.Context) &&
2852+
!Succeeded) {
2853+
FailedCond = Term->IgnoreParenImpCasts();
2854+
break;
2855+
}
2856+
}
2857+
2858+
if (!FailedCond)
2859+
FailedCond = Cond->IgnoreParenImpCasts();
2860+
2861+
std::string Description;
2862+
{
2863+
llvm::raw_string_ostream Out(Description);
2864+
FailedCond->printPretty(Out, nullptr,
2865+
PrintingPolicy(S.Context.getLangOpts()));
2866+
}
2867+
return { FailedCond, Description };
2868+
}
2869+
28092870
QualType Sema::CheckTemplateIdType(TemplateName Name,
28102871
SourceLocation TemplateLoc,
28112872
TemplateArgumentListInfo &TemplateArgs) {
@@ -2852,12 +2913,12 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
28522913
if (Pattern->isInvalidDecl())
28532914
return QualType();
28542915

2855-
TemplateArgumentList TemplateArgs(TemplateArgumentList::OnStack,
2856-
Converted);
2916+
TemplateArgumentList StackTemplateArgs(TemplateArgumentList::OnStack,
2917+
Converted);
28572918

28582919
// Only substitute for the innermost template argument list.
28592920
MultiLevelTemplateArgumentList TemplateArgLists;
2860-
TemplateArgLists.addOuterTemplateArguments(&TemplateArgs);
2921+
TemplateArgLists.addOuterTemplateArguments(&StackTemplateArgs);
28612922
unsigned Depth = AliasTemplate->getTemplateParameters()->getDepth();
28622923
for (unsigned I = 0; I < Depth; ++I)
28632924
TemplateArgLists.addOuterTemplateArguments(None);
@@ -2870,8 +2931,42 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
28702931
CanonType = SubstType(Pattern->getUnderlyingType(),
28712932
TemplateArgLists, AliasTemplate->getLocation(),
28722933
AliasTemplate->getDeclName());
2873-
if (CanonType.isNull())
2934+
if (CanonType.isNull()) {
2935+
// If this was enable_if and we failed to find the nested type
2936+
// within enable_if in a SFINAE context, dig out the specific
2937+
// enable_if condition that failed and present that instead.
2938+
if (isEnableIfAliasTemplate(AliasTemplate)) {
2939+
if (auto DeductionInfo = isSFINAEContext()) {
2940+
if (*DeductionInfo &&
2941+
(*DeductionInfo)->hasSFINAEDiagnostic() &&
2942+
(*DeductionInfo)->peekSFINAEDiagnostic().second.getDiagID() ==
2943+
diag::err_typename_nested_not_found_enable_if &&
2944+
TemplateArgs[0].getArgument().getKind()
2945+
== TemplateArgument::Expression) {
2946+
Expr *FailedCond;
2947+
std::string FailedDescription;
2948+
std::tie(FailedCond, FailedDescription) =
2949+
findFailedEnableIfCondition(
2950+
*this, TemplateArgs[0].getSourceExpression());
2951+
2952+
// Remove the old SFINAE diagnostic.
2953+
PartialDiagnosticAt OldDiag =
2954+
{SourceLocation(), PartialDiagnostic::NullDiagnostic()};
2955+
(*DeductionInfo)->takeSFINAEDiagnostic(OldDiag);
2956+
2957+
// Add a new SFINAE diagnostic specifying which condition
2958+
// failed.
2959+
(*DeductionInfo)->addSFINAEDiagnostic(
2960+
OldDiag.first,
2961+
PDiag(diag::err_typename_nested_not_found_requirement)
2962+
<< FailedDescription
2963+
<< FailedCond->getSourceRange());
2964+
}
2965+
}
2966+
}
2967+
28742968
return QualType();
2969+
}
28752970
} else if (Name.isDependent() ||
28762971
TemplateSpecializationType::anyDependentTemplateArguments(
28772972
TemplateArgs, InstantiationDependent)) {
@@ -9290,7 +9385,7 @@ Sema::ActOnTypenameType(Scope *S,
92909385
/// Determine whether this failed name lookup should be treated as being
92919386
/// disabled by a usage of std::enable_if.
92929387
static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
9293-
SourceRange &CondRange) {
9388+
SourceRange &CondRange, Expr *&Cond) {
92949389
// We must be looking for a ::type...
92959390
if (!II.isStr("type"))
92969391
return false;
@@ -9320,6 +9415,19 @@ static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
93209415

93219416
// Assume the first template argument is the condition.
93229417
CondRange = EnableIfTSTLoc.getArgLoc(0).getSourceRange();
9418+
9419+
// Dig out the condition.
9420+
Cond = nullptr;
9421+
if (EnableIfTSTLoc.getArgLoc(0).getArgument().getKind()
9422+
!= TemplateArgument::Expression)
9423+
return true;
9424+
9425+
Cond = EnableIfTSTLoc.getArgLoc(0).getSourceExpression();
9426+
9427+
// Ignore Boolean literals; they add no value.
9428+
if (isa<CXXBoolLiteralExpr>(Cond->IgnoreParenCasts()))
9429+
Cond = nullptr;
9430+
93239431
return true;
93249432
}
93259433

@@ -9363,9 +9471,25 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
93639471
// If we're looking up 'type' within a template named 'enable_if', produce
93649472
// a more specific diagnostic.
93659473
SourceRange CondRange;
9366-
if (isEnableIf(QualifierLoc, II, CondRange)) {
9474+
Expr *Cond = nullptr;
9475+
if (isEnableIf(QualifierLoc, II, CondRange, Cond)) {
9476+
// If we have a condition, narrow it down to the specific failed
9477+
// condition.
9478+
if (Cond) {
9479+
Expr *FailedCond;
9480+
std::string FailedDescription;
9481+
std::tie(FailedCond, FailedDescription) =
9482+
findFailedEnableIfCondition(*this, Cond);
9483+
9484+
Diag(FailedCond->getExprLoc(),
9485+
diag::err_typename_nested_not_found_requirement)
9486+
<< FailedDescription
9487+
<< FailedCond->getSourceRange();
9488+
return QualType();
9489+
}
9490+
93679491
Diag(CondRange.getBegin(), diag::err_typename_nested_not_found_enable_if)
9368-
<< Ctx << CondRange;
9492+
<< Ctx << CondRange;
93699493
return QualType();
93709494
}
93719495

test/SemaTemplate/constexpr-instantiate.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ namespace Unevaluated {
191191
static constexpr bool f() { return sizeof(T) < U::size; }
192192

193193
template<typename U>
194-
static typename enable_if<f<U>(), void>::type g() {} // expected-note {{disabled by 'enable_if'}}
194+
static typename enable_if<f<U>(), void>::type g() {} // expected-note {{requirement 'f<Unevaluated::PR13423::U>()' was not satisfied}}
195195
};
196196

197197
struct U { static constexpr int size = 2; };

test/SemaTemplate/overload-candidates.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ namespace boost {
4747
template<bool, typename = void> struct enable_if {};
4848
template<typename T> struct enable_if<true, T> { typedef T type; };
4949
}
50-
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]}}
50+
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]}}
5151
int k = if_size_4<char>(); // expected-error{{no matching function}}
5252

5353
namespace llvm {
@@ -61,7 +61,7 @@ void test_if_int() {
6161
}
6262

6363
template<typename T> struct NonTemplateFunction {
64-
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}}
64+
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}}
6565
};
6666
NonTemplateFunction<char> NTFC; // expected-note{{here}}
6767

@@ -100,7 +100,7 @@ namespace PR15673 {
100100
#if __cplusplus <= 199711L
101101
// expected-warning@-2 {{default template arguments for a function template are a C++11 extension}}
102102
#endif
103-
// expected-note@-4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
103+
// expected-note@+1 {{candidate template ignored: requirement 'a_trait<int>::value' was not satisfied [with T = int]}}
104104
void foo() {}
105105
void bar() { foo<int>(); } // expected-error {{no matching function for call to 'foo'}}
106106

@@ -128,7 +128,7 @@ namespace PR15673 {
128128
#if __cplusplus <= 199711L
129129
// expected-warning@-2 {{alias declarations are a C++11 extension}}
130130
#endif
131-
// expected-note@-4 {{candidate template ignored: disabled by 'enable_if' [with T = int]}}
131+
// expected-note@+7 {{candidate template ignored: requirement 'some_trait<int>::value' was not satisfied [with T = int]}}
132132

133133
template<typename T,
134134
typename Requires = unicorns<T> >

0 commit comments

Comments
 (0)