-
Notifications
You must be signed in to change notification settings - Fork 14.5k
[Clang] Explain why a type trait evaluated to false. #141238
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1917,3 +1917,190 @@ ExprResult Sema::BuildExpressionTrait(ExpressionTrait ET, SourceLocation KWLoc, | |
return new (Context) | ||
ExpressionTraitExpr(KWLoc, ET, Queried, Value, RParen, Context.BoolTy); | ||
} | ||
|
||
static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { | ||
return llvm::StringSwitch<std::optional<TypeTrait>>(Name) | ||
.Case("is_trivially_relocatable", | ||
TypeTrait::UTT_IsCppTriviallyRelocatable) | ||
.Default(std::nullopt); | ||
} | ||
|
||
using ExtractedTypeTraitInfo = | ||
std::optional<std::pair<TypeTrait, llvm::SmallVector<QualType, 1>>>; | ||
|
||
// Recognize type traits that are builting type traits, or known standard | ||
// type traits in <type_traits>. Note that at this point we assume the | ||
// trait evaluated to false, so we need only to recognize the shape of the | ||
// outer-most symbol. | ||
static ExtractedTypeTraitInfo ExtractTypeTraitFromExpression(const Expr *E) { | ||
llvm::SmallVector<QualType, 1> Args; | ||
std::optional<TypeTrait> Trait; | ||
|
||
// builtins | ||
if (const auto *TraitExpr = dyn_cast<TypeTraitExpr>(E)) { | ||
Trait = TraitExpr->getTrait(); | ||
for (const auto *Arg : TraitExpr->getArgs()) | ||
Args.push_back(Arg->getType()); | ||
return {{Trait.value(), std::move(Args)}}; | ||
} | ||
const auto *Ref = dyn_cast<DeclRefExpr>(E); | ||
if (!Ref) | ||
return std::nullopt; | ||
|
||
// std::is_xxx_v<> | ||
if (const auto *VD = | ||
dyn_cast<VarTemplateSpecializationDecl>(Ref->getDecl())) { | ||
if (!VD->isInStdNamespace()) | ||
return std::nullopt; | ||
StringRef Name = VD->getIdentifier()->getName(); | ||
if (!Name.consume_back("_v")) | ||
return std::nullopt; | ||
Trait = StdNameToTypeTrait(Name); | ||
if (!Trait) | ||
return std::nullopt; | ||
for (const auto &Arg : VD->getTemplateArgs().asArray()) | ||
Args.push_back(Arg.getAsType()); | ||
return {{Trait.value(), std::move(Args)}}; | ||
} | ||
|
||
// std::is_xxx<>::value | ||
if (const auto *VD = dyn_cast<VarDecl>(Ref->getDecl()); | ||
Ref->hasQualifier() && VD && VD->getIdentifier()->isStr("value")) { | ||
const Type *T = Ref->getQualifier()->getAsType(); | ||
if (!T) | ||
return std::nullopt; | ||
const TemplateSpecializationType *Ts = | ||
T->getAs<TemplateSpecializationType>(); | ||
if (!Ts) | ||
return std::nullopt; | ||
const TemplateDecl *D = Ts->getTemplateName().getAsTemplateDecl(); | ||
if (!D || !D->isInStdNamespace()) | ||
return std::nullopt; | ||
Trait = StdNameToTypeTrait(D->getIdentifier()->getName()); | ||
if (!Trait) | ||
return std::nullopt; | ||
for (const auto &Arg : Ts->template_arguments()) | ||
Args.push_back(Arg.getAsType()); | ||
return {{Trait.value(), std::move(Args)}}; | ||
} | ||
return std::nullopt; | ||
} | ||
|
||
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, | ||
SourceLocation Loc, | ||
const CXXRecordDecl *D) { | ||
for (const CXXBaseSpecifier &B : D->bases()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do a |
||
const auto *BaseDecl = B.getType()->getAsCXXRecordDecl(); | ||
if (!BaseDecl) | ||
continue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .... CAN this happen? |
||
if (B.isVirtual()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::VBase << B.getType() | ||
<< B.getSourceRange(); | ||
if (!SemaRef.IsCXXTriviallyRelocatableType(B.getType())) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::NRBase << B.getType() | ||
<< B.getSourceRange(); | ||
} | ||
for (const FieldDecl *Field : D->fields()) { | ||
if (Field->getType()->isReferenceType()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might suggest combining the two 'if' statements. |
||
continue; | ||
if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType())) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::NRField << Field << Field->getType() | ||
<< Field->getSourceRange(); | ||
} | ||
if (D->hasDeletedDestructor()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0 | ||
<< D->getDestructor()->getSourceRange(); | ||
|
||
if (D->hasAttr<TriviallyRelocatableAttr>()) | ||
return; | ||
|
||
if (D->isUnion()) { | ||
auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) { | ||
if (Has) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::UnionWithUserDeclaredSMF << K; | ||
}; | ||
DiagSPM(CXXSpecialMemberKind::CopyConstructor, | ||
D->hasUserDeclaredCopyConstructor()); | ||
DiagSPM(CXXSpecialMemberKind::CopyAssignment, | ||
D->hasUserDeclaredCopyAssignment()); | ||
DiagSPM(CXXSpecialMemberKind::MoveConstructor, | ||
D->hasUserDeclaredMoveConstructor()); | ||
DiagSPM(CXXSpecialMemberKind::MoveAssignment, | ||
D->hasUserDeclaredMoveAssignment()); | ||
return; | ||
} | ||
|
||
if (!D->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) { | ||
const auto *Decl = cast<CXXConstructorDecl>( | ||
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false)); | ||
if (Decl && Decl->isUserProvided()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::UserProvidedCtr | ||
<< Decl->isMoveConstructor() << Decl->getSourceRange(); | ||
} | ||
if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) { | ||
CXXMethodDecl *Decl = | ||
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true); | ||
if (Decl && Decl->isUserProvided()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::UserProvidedAssign | ||
<< Decl->isMoveAssignmentOperator() << Decl->getSourceRange(); | ||
} | ||
CXXDestructorDecl *Dtr = D->getDestructor(); | ||
if (Dtr && Dtr->isUserProvided() && !Dtr->isDefaulted()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*User Provided*/ 1 | ||
<< Dtr->getSourceRange(); | ||
} | ||
|
||
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef, | ||
SourceLocation Loc, | ||
QualType T) { | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait) | ||
<< T << diag::TraitName::TriviallyRelocatable; | ||
if (T->isVariablyModifiedType()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::VLA; | ||
|
||
if (T->isReferenceType()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::Ref; | ||
T = T.getNonReferenceType(); | ||
|
||
if (T.hasNonTrivialObjCLifetime()) | ||
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) | ||
<< diag::TraitNotSatisfiedReason::HasArcLifetime; | ||
|
||
const CXXRecordDecl *D = T->getAsCXXRecordDecl(); | ||
if (!D) | ||
return; | ||
|
||
if (D->hasDefinition()) | ||
DiagnoseNonTriviallyRelocatableReason(SemaRef, Loc, D); | ||
|
||
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; | ||
} | ||
|
||
void Sema::DiagnoseTypeTraitDetails(const Expr *E) { | ||
E = E->IgnoreParenImpCasts(); | ||
if (E->containsErrors()) | ||
return; | ||
|
||
ExtractedTypeTraitInfo TraitInfo = ExtractTypeTraitFromExpression(E); | ||
if (!TraitInfo) | ||
return; | ||
|
||
const auto &[Trait, Args] = TraitInfo.value(); | ||
switch (Trait) { | ||
case UTT_IsCppTriviallyRelocatable: | ||
DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]); | ||
break; | ||
default: | ||
break; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD1 %s | ||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD2 %s | ||
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD3 %s | ||
|
||
namespace std { | ||
|
||
#ifdef STD1 | ||
template <typename T> | ||
struct is_trivially_relocatable { | ||
static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T); | ||
}; | ||
|
||
template <typename T> | ||
constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T); | ||
#endif | ||
|
||
#ifdef STD2 | ||
template <typename T> | ||
struct __details_is_trivially_relocatable { | ||
static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T); | ||
}; | ||
|
||
template <typename T> | ||
using is_trivially_relocatable = __details_is_trivially_relocatable<T>; | ||
|
||
template <typename T> | ||
constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T); | ||
#endif | ||
|
||
|
||
#ifdef STD3 | ||
template< class T, T v > | ||
struct integral_constant { | ||
static constexpr T value = v; | ||
}; | ||
|
||
template< bool B > | ||
using bool_constant = integral_constant<bool, B>; | ||
|
||
template <typename T> | ||
struct __details_is_trivially_relocatable : bool_constant<__builtin_is_cpp_trivially_relocatable(T)> {}; | ||
|
||
template <typename T> | ||
using is_trivially_relocatable = __details_is_trivially_relocatable<T>; | ||
|
||
template <typename T> | ||
constexpr bool is_trivially_relocatable_v = is_trivially_relocatable<T>::value; | ||
#endif | ||
|
||
} | ||
|
||
static_assert(std::is_trivially_relocatable<int>::value); | ||
|
||
static_assert(std::is_trivially_relocatable<int&>::value); | ||
// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_trivially_relocatable<int &>::value'}} \ | ||
// expected-note@-1 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@-1 {{because it is a reference type}} | ||
static_assert(std::is_trivially_relocatable_v<int&>); | ||
// expected-error@-1 {{static assertion failed due to requirement 'std::is_trivially_relocatable_v<int &>'}} \ | ||
// expected-note@-1 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@-1 {{because it is a reference type}} | ||
|
||
namespace test_namespace { | ||
using namespace std; | ||
static_assert(is_trivially_relocatable<int&>::value); | ||
// expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_trivially_relocatable<int &>::value'}} \ | ||
// expected-note@-1 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@-1 {{because it is a reference type}} | ||
static_assert(is_trivially_relocatable_v<int&>); | ||
// expected-error@-1 {{static assertion failed due to requirement 'is_trivially_relocatable_v<int &>'}} \ | ||
// expected-note@-1 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@-1 {{because it is a reference type}} | ||
} | ||
|
||
|
||
namespace concepts { | ||
template <typename T> | ||
requires std::is_trivially_relocatable<T>::value void f(); // #cand1 | ||
|
||
template <typename T> | ||
concept C = std::is_trivially_relocatable_v<T>; // #concept2 | ||
|
||
template <C T> void g(); // #cand2 | ||
|
||
void test() { | ||
f<int&>(); | ||
// expected-error@-1 {{no matching function for call to 'f'}} \ | ||
// expected-note@#cand1 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ | ||
// expected-note-re@#cand1 {{because '{{.*}}is_trivially_relocatable<int &>::value' evaluated to false}} \ | ||
// expected-note@#cand1 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@#cand1 {{because it is a reference type}} | ||
|
||
g<int&>(); | ||
// expected-error@-1 {{no matching function for call to 'g'}} \ | ||
// expected-note@#cand2 {{candidate template ignored: constraints not satisfied [with T = int &]}} \ | ||
// expected-note@#cand2 {{because 'int &' does not satisfy 'C'}} \ | ||
// expected-note@#concept2 {{because 'std::is_trivially_relocatable_v<int &>' evaluated to false}} \ | ||
// expected-note@#concept2 {{'int &' is not trivially relocatable}} \ | ||
// expected-note@#concept2 {{because it is a reference type}} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My word... I'm VERY glad we have
enum_select
:DThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, in these cases it's super useful, thanks :D