mirror of https://github.com/microsoft/clang.git
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
This commit is contained in:
parent
29e21c348d
commit
d93c41c8dc
|
@ -3518,6 +3518,8 @@ def note_ovl_candidate_substitution_failure : Note<
|
||||||
"candidate template ignored: substitution failure%0%1">;
|
"candidate template ignored: substitution failure%0%1">;
|
||||||
def note_ovl_candidate_disabled_by_enable_if : Note<
|
def note_ovl_candidate_disabled_by_enable_if : Note<
|
||||||
"candidate template ignored: disabled by %0%1">;
|
"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<
|
def note_ovl_candidate_has_pass_object_size_params: Note<
|
||||||
"candidate address cannot be taken because parameter %0 has "
|
"candidate address cannot be taken because parameter %0 has "
|
||||||
"pass_object_size attribute">;
|
"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<
|
def err_typename_nested_not_found_enable_if : Error<
|
||||||
"no type named 'type' in %0; 'enable_if' cannot be used to disable "
|
"no type named 'type' in %0; 'enable_if' cannot be used to disable "
|
||||||
"this declaration">;
|
"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<
|
def err_typename_nested_not_type : Error<
|
||||||
"typename specifier refers to non-type member %0 in %1">;
|
"typename specifier refers to non-type member %0 in %1">;
|
||||||
def note_typename_refers_here : Note<
|
def note_typename_refers_here : Note<
|
||||||
|
|
|
@ -329,6 +329,15 @@ public:
|
||||||
|
|
||||||
bool hasStorage() const { return DiagStorage != nullptr; }
|
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,
|
friend const PartialDiagnostic &operator<<(const PartialDiagnostic &PD,
|
||||||
unsigned I) {
|
unsigned I) {
|
||||||
PD.AddTaggedVal(I, DiagnosticsEngine::ak_uint);
|
PD.AddTaggedVal(I, DiagnosticsEngine::ak_uint);
|
||||||
|
|
|
@ -88,6 +88,12 @@ public:
|
||||||
HasSFINAEDiagnostic = false;
|
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
|
/// \brief Provide a new template argument list that contains the
|
||||||
/// results of template argument deduction.
|
/// results of template argument deduction.
|
||||||
void reset(TemplateArgumentList *NewDeduced) {
|
void reset(TemplateArgumentList *NewDeduced) {
|
||||||
|
|
|
@ -9830,6 +9830,15 @@ static void DiagnoseBadDeduction(Sema &S, NamedDecl *Found, Decl *Templated,
|
||||||
return;
|
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.
|
// Format the SFINAE diagnostic into the argument string.
|
||||||
// FIXME: Add a general mechanism to include a PartialDiagnostic *'s
|
// FIXME: Add a general mechanism to include a PartialDiagnostic *'s
|
||||||
// formatted message in another diagnostic.
|
// formatted message in another diagnostic.
|
||||||
|
|
|
@ -2806,6 +2806,67 @@ checkBuiltinTemplateIdType(Sema &SemaRef, BuiltinTemplateDecl *BTD,
|
||||||
llvm_unreachable("unexpected BuiltinTemplateDecl!");
|
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,
|
QualType Sema::CheckTemplateIdType(TemplateName Name,
|
||||||
SourceLocation TemplateLoc,
|
SourceLocation TemplateLoc,
|
||||||
TemplateArgumentListInfo &TemplateArgs) {
|
TemplateArgumentListInfo &TemplateArgs) {
|
||||||
|
@ -2852,12 +2913,12 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
|
||||||
if (Pattern->isInvalidDecl())
|
if (Pattern->isInvalidDecl())
|
||||||
return QualType();
|
return QualType();
|
||||||
|
|
||||||
TemplateArgumentList TemplateArgs(TemplateArgumentList::OnStack,
|
TemplateArgumentList StackTemplateArgs(TemplateArgumentList::OnStack,
|
||||||
Converted);
|
Converted);
|
||||||
|
|
||||||
// Only substitute for the innermost template argument list.
|
// Only substitute for the innermost template argument list.
|
||||||
MultiLevelTemplateArgumentList TemplateArgLists;
|
MultiLevelTemplateArgumentList TemplateArgLists;
|
||||||
TemplateArgLists.addOuterTemplateArguments(&TemplateArgs);
|
TemplateArgLists.addOuterTemplateArguments(&StackTemplateArgs);
|
||||||
unsigned Depth = AliasTemplate->getTemplateParameters()->getDepth();
|
unsigned Depth = AliasTemplate->getTemplateParameters()->getDepth();
|
||||||
for (unsigned I = 0; I < Depth; ++I)
|
for (unsigned I = 0; I < Depth; ++I)
|
||||||
TemplateArgLists.addOuterTemplateArguments(None);
|
TemplateArgLists.addOuterTemplateArguments(None);
|
||||||
|
@ -2870,8 +2931,42 @@ QualType Sema::CheckTemplateIdType(TemplateName Name,
|
||||||
CanonType = SubstType(Pattern->getUnderlyingType(),
|
CanonType = SubstType(Pattern->getUnderlyingType(),
|
||||||
TemplateArgLists, AliasTemplate->getLocation(),
|
TemplateArgLists, AliasTemplate->getLocation(),
|
||||||
AliasTemplate->getDeclName());
|
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();
|
return QualType();
|
||||||
|
}
|
||||||
} else if (Name.isDependent() ||
|
} else if (Name.isDependent() ||
|
||||||
TemplateSpecializationType::anyDependentTemplateArguments(
|
TemplateSpecializationType::anyDependentTemplateArguments(
|
||||||
TemplateArgs, InstantiationDependent)) {
|
TemplateArgs, InstantiationDependent)) {
|
||||||
|
@ -9290,7 +9385,7 @@ Sema::ActOnTypenameType(Scope *S,
|
||||||
/// Determine whether this failed name lookup should be treated as being
|
/// Determine whether this failed name lookup should be treated as being
|
||||||
/// disabled by a usage of std::enable_if.
|
/// disabled by a usage of std::enable_if.
|
||||||
static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
|
static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
|
||||||
SourceRange &CondRange) {
|
SourceRange &CondRange, Expr *&Cond) {
|
||||||
// We must be looking for a ::type...
|
// We must be looking for a ::type...
|
||||||
if (!II.isStr("type"))
|
if (!II.isStr("type"))
|
||||||
return false;
|
return false;
|
||||||
|
@ -9320,6 +9415,19 @@ static bool isEnableIf(NestedNameSpecifierLoc NNS, const IdentifierInfo &II,
|
||||||
|
|
||||||
// Assume the first template argument is the condition.
|
// Assume the first template argument is the condition.
|
||||||
CondRange = EnableIfTSTLoc.getArgLoc(0).getSourceRange();
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9363,9 +9471,25 @@ Sema::CheckTypenameType(ElaboratedTypeKeyword Keyword,
|
||||||
// If we're looking up 'type' within a template named 'enable_if', produce
|
// If we're looking up 'type' within a template named 'enable_if', produce
|
||||||
// a more specific diagnostic.
|
// a more specific diagnostic.
|
||||||
SourceRange CondRange;
|
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)
|
Diag(CondRange.getBegin(), diag::err_typename_nested_not_found_enable_if)
|
||||||
<< Ctx << CondRange;
|
<< Ctx << CondRange;
|
||||||
return QualType();
|
return QualType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ namespace Unevaluated {
|
||||||
static constexpr bool f() { return sizeof(T) < U::size; }
|
static constexpr bool f() { return sizeof(T) < U::size; }
|
||||||
|
|
||||||
template<typename U>
|
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; };
|
struct U { static constexpr int size = 2; };
|
||||||
|
|
|
@ -47,7 +47,7 @@ namespace boost {
|
||||||
template<bool, typename = void> struct enable_if {};
|
template<bool, typename = void> struct enable_if {};
|
||||||
template<typename T> struct enable_if<true, T> { typedef T type; };
|
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}}
|
int k = if_size_4<char>(); // expected-error{{no matching function}}
|
||||||
|
|
||||||
namespace llvm {
|
namespace llvm {
|
||||||
|
@ -61,7 +61,7 @@ void test_if_int() {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T> struct NonTemplateFunction {
|
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}}
|
NonTemplateFunction<char> NTFC; // expected-note{{here}}
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ namespace PR15673 {
|
||||||
#if __cplusplus <= 199711L
|
#if __cplusplus <= 199711L
|
||||||
// expected-warning@-2 {{default template arguments for a function template are a C++11 extension}}
|
// expected-warning@-2 {{default template arguments for a function template are a C++11 extension}}
|
||||||
#endif
|
#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 foo() {}
|
||||||
void bar() { foo<int>(); } // expected-error {{no matching function for call to 'foo'}}
|
void bar() { foo<int>(); } // expected-error {{no matching function for call to 'foo'}}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ namespace PR15673 {
|
||||||
#if __cplusplus <= 199711L
|
#if __cplusplus <= 199711L
|
||||||
// expected-warning@-2 {{alias declarations are a C++11 extension}}
|
// expected-warning@-2 {{alias declarations are a C++11 extension}}
|
||||||
#endif
|
#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,
|
template<typename T,
|
||||||
typename Requires = unicorns<T> >
|
typename Requires = unicorns<T> >
|
||||||
|
|
Loading…
Reference in New Issue