Skip to content

Commit 0804ca8

Browse files
authored
[Clang] Explain why a type trait evaluated to false. (#141238)
`static_assert(std::is_xx_v<MyType>);` is a common pattern to check that a type meets a requirement. This patch produces diagnostics notes when such assertion fails. The first type trait for which we provide detailed explanation is std::is_trivially_relocatable. We employ the same mechanisn when a type trait appears an an unsatisfied atomic constraint. I plan to also support `std::is_trivially_replaceable` in a follow up PR, and hopefully, over time we can support more type traits.
1 parent a6ca703 commit 0804ca8

File tree

8 files changed

+469
-1
lines changed

8 files changed

+469
-1
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1762,6 +1762,29 @@ def err_user_defined_msg_constexpr : Error<
17621762
"%sub{subst_user_defined_msg}0 must be produced by a "
17631763
"constant expression">;
17641764

1765+
// Type traits explanations
1766+
def note_unsatisfied_trait : Note<"%0 is not %enum_select<TraitName>{"
1767+
"%TriviallyRelocatable{trivially relocatable}"
1768+
"}1">;
1769+
1770+
def note_unsatisfied_trait_reason
1771+
: Note<"because it "
1772+
"%enum_select<TraitNotSatisfiedReason>{"
1773+
"%Ref{is a reference type}|"
1774+
"%HasArcLifetime{has an ARC lifetime qualifier}|"
1775+
"%VLA{is a variably-modified type}|"
1776+
"%VBase{has a virtual base %1}|"
1777+
"%NRBase{has a non-trivially-relocatable base %1}|"
1778+
"%NRField{has a non-trivially-relocatable member %1 of type %2}|"
1779+
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
1780+
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
1781+
"constructor}|"
1782+
"%UserProvidedAssign{has a user provided %select{copy|move}1 "
1783+
"assignment operator}|"
1784+
"%UnionWithUserDeclaredSMF{is a union with a user-declared "
1785+
"%sub{select_special_member_kind}1}"
1786+
"}0">;
1787+
17651788
def warn_consteval_if_always_true : Warning<
17661789
"consteval if is always true in an %select{unevaluated|immediate}0 context">,
17671790
InGroup<DiagGroup<"redundant-consteval-if">>;

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5910,6 +5910,11 @@ class Sema final : public SemaBase {
59105910
/// with expression \E
59115911
void DiagnoseStaticAssertDetails(const Expr *E);
59125912

5913+
/// If E represents a built-in type trait, or a known standard type trait,
5914+
/// try to print more information about why the type type-trait failed.
5915+
/// This assumes we already evaluated the expression to a false boolean value.
5916+
void DiagnoseTypeTraitDetails(const Expr *E);
5917+
59135918
/// Handle a friend type declaration. This works in tandem with
59145919
/// ActOnTag.
59155920
///

clang/lib/Sema/SemaConcept.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,6 +1320,7 @@ static void diagnoseWellFormedUnsatisfiedConstraintExpr(Sema &S,
13201320
S.Diag(SubstExpr->getSourceRange().getBegin(),
13211321
diag::note_atomic_constraint_evaluated_to_false)
13221322
<< (int)First << SubstExpr;
1323+
S.DiagnoseTypeTraitDetails(SubstExpr);
13231324
}
13241325

13251326
template <typename SubstitutionDiagnostic>

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17694,6 +17694,8 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
1769417694
<< DiagSide[0].ValueString << Op->getOpcodeStr()
1769517695
<< DiagSide[1].ValueString << Op->getSourceRange();
1769617696
}
17697+
} else {
17698+
DiagnoseTypeTraitDetails(E);
1769717699
}
1769817700
}
1769917701

clang/lib/Sema/SemaTypeTraits.cpp

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,3 +1917,188 @@ ExprResult Sema::BuildExpressionTrait(ExpressionTrait ET, SourceLocation KWLoc,
19171917
return new (Context)
19181918
ExpressionTraitExpr(KWLoc, ET, Queried, Value, RParen, Context.BoolTy);
19191919
}
1920+
1921+
static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
1922+
return llvm::StringSwitch<std::optional<TypeTrait>>(Name)
1923+
.Case("is_trivially_relocatable",
1924+
TypeTrait::UTT_IsCppTriviallyRelocatable)
1925+
.Default(std::nullopt);
1926+
}
1927+
1928+
using ExtractedTypeTraitInfo =
1929+
std::optional<std::pair<TypeTrait, llvm::SmallVector<QualType, 1>>>;
1930+
1931+
// Recognize type traits that are builting type traits, or known standard
1932+
// type traits in <type_traits>. Note that at this point we assume the
1933+
// trait evaluated to false, so we need only to recognize the shape of the
1934+
// outer-most symbol.
1935+
static ExtractedTypeTraitInfo ExtractTypeTraitFromExpression(const Expr *E) {
1936+
llvm::SmallVector<QualType, 1> Args;
1937+
std::optional<TypeTrait> Trait;
1938+
1939+
// builtins
1940+
if (const auto *TraitExpr = dyn_cast<TypeTraitExpr>(E)) {
1941+
Trait = TraitExpr->getTrait();
1942+
for (const auto *Arg : TraitExpr->getArgs())
1943+
Args.push_back(Arg->getType());
1944+
return {{Trait.value(), std::move(Args)}};
1945+
}
1946+
const auto *Ref = dyn_cast<DeclRefExpr>(E);
1947+
if (!Ref)
1948+
return std::nullopt;
1949+
1950+
// std::is_xxx_v<>
1951+
if (const auto *VD =
1952+
dyn_cast<VarTemplateSpecializationDecl>(Ref->getDecl())) {
1953+
if (!VD->isInStdNamespace())
1954+
return std::nullopt;
1955+
StringRef Name = VD->getIdentifier()->getName();
1956+
if (!Name.consume_back("_v"))
1957+
return std::nullopt;
1958+
Trait = StdNameToTypeTrait(Name);
1959+
if (!Trait)
1960+
return std::nullopt;
1961+
for (const auto &Arg : VD->getTemplateArgs().asArray())
1962+
Args.push_back(Arg.getAsType());
1963+
return {{Trait.value(), std::move(Args)}};
1964+
}
1965+
1966+
// std::is_xxx<>::value
1967+
if (const auto *VD = dyn_cast<VarDecl>(Ref->getDecl());
1968+
Ref->hasQualifier() && VD && VD->getIdentifier()->isStr("value")) {
1969+
const Type *T = Ref->getQualifier()->getAsType();
1970+
if (!T)
1971+
return std::nullopt;
1972+
const TemplateSpecializationType *Ts =
1973+
T->getAs<TemplateSpecializationType>();
1974+
if (!Ts)
1975+
return std::nullopt;
1976+
const TemplateDecl *D = Ts->getTemplateName().getAsTemplateDecl();
1977+
if (!D || !D->isInStdNamespace())
1978+
return std::nullopt;
1979+
Trait = StdNameToTypeTrait(D->getIdentifier()->getName());
1980+
if (!Trait)
1981+
return std::nullopt;
1982+
for (const auto &Arg : Ts->template_arguments())
1983+
Args.push_back(Arg.getAsType());
1984+
return {{Trait.value(), std::move(Args)}};
1985+
}
1986+
return std::nullopt;
1987+
}
1988+
1989+
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
1990+
SourceLocation Loc,
1991+
const CXXRecordDecl *D) {
1992+
for (const CXXBaseSpecifier &B : D->bases()) {
1993+
const auto *BaseDecl = B.getType()->getAsCXXRecordDecl();
1994+
assert(BaseDecl && "invalid base?");
1995+
if (B.isVirtual())
1996+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
1997+
<< diag::TraitNotSatisfiedReason::VBase << B.getType()
1998+
<< B.getSourceRange();
1999+
if (!SemaRef.IsCXXTriviallyRelocatableType(B.getType()))
2000+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2001+
<< diag::TraitNotSatisfiedReason::NRBase << B.getType()
2002+
<< B.getSourceRange();
2003+
}
2004+
for (const FieldDecl *Field : D->fields()) {
2005+
if (!Field->getType()->isReferenceType() &&
2006+
!SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
2007+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2008+
<< diag::TraitNotSatisfiedReason::NRField << Field << Field->getType()
2009+
<< Field->getSourceRange();
2010+
}
2011+
if (D->hasDeletedDestructor())
2012+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2013+
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0
2014+
<< D->getDestructor()->getSourceRange();
2015+
2016+
if (D->hasAttr<TriviallyRelocatableAttr>())
2017+
return;
2018+
2019+
if (D->isUnion()) {
2020+
auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) {
2021+
if (Has)
2022+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2023+
<< diag::TraitNotSatisfiedReason::UnionWithUserDeclaredSMF << K;
2024+
};
2025+
DiagSPM(CXXSpecialMemberKind::CopyConstructor,
2026+
D->hasUserDeclaredCopyConstructor());
2027+
DiagSPM(CXXSpecialMemberKind::CopyAssignment,
2028+
D->hasUserDeclaredCopyAssignment());
2029+
DiagSPM(CXXSpecialMemberKind::MoveConstructor,
2030+
D->hasUserDeclaredMoveConstructor());
2031+
DiagSPM(CXXSpecialMemberKind::MoveAssignment,
2032+
D->hasUserDeclaredMoveAssignment());
2033+
return;
2034+
}
2035+
2036+
if (!D->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) {
2037+
const auto *Decl = cast<CXXConstructorDecl>(
2038+
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false));
2039+
if (Decl && Decl->isUserProvided())
2040+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2041+
<< diag::TraitNotSatisfiedReason::UserProvidedCtr
2042+
<< Decl->isMoveConstructor() << Decl->getSourceRange();
2043+
}
2044+
if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) {
2045+
CXXMethodDecl *Decl =
2046+
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true);
2047+
if (Decl && Decl->isUserProvided())
2048+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2049+
<< diag::TraitNotSatisfiedReason::UserProvidedAssign
2050+
<< Decl->isMoveAssignmentOperator() << Decl->getSourceRange();
2051+
}
2052+
CXXDestructorDecl *Dtr = D->getDestructor();
2053+
if (Dtr && Dtr->isUserProvided() && !Dtr->isDefaulted())
2054+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2055+
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*User Provided*/ 1
2056+
<< Dtr->getSourceRange();
2057+
}
2058+
2059+
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
2060+
SourceLocation Loc,
2061+
QualType T) {
2062+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
2063+
<< T << diag::TraitName::TriviallyRelocatable;
2064+
if (T->isVariablyModifiedType())
2065+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2066+
<< diag::TraitNotSatisfiedReason::VLA;
2067+
2068+
if (T->isReferenceType())
2069+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2070+
<< diag::TraitNotSatisfiedReason::Ref;
2071+
T = T.getNonReferenceType();
2072+
2073+
if (T.hasNonTrivialObjCLifetime())
2074+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2075+
<< diag::TraitNotSatisfiedReason::HasArcLifetime;
2076+
2077+
const CXXRecordDecl *D = T->getAsCXXRecordDecl();
2078+
if (!D || D->isInvalidDecl())
2079+
return;
2080+
2081+
if (D->hasDefinition())
2082+
DiagnoseNonTriviallyRelocatableReason(SemaRef, Loc, D);
2083+
2084+
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
2085+
}
2086+
2087+
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
2088+
E = E->IgnoreParenImpCasts();
2089+
if (E->containsErrors())
2090+
return;
2091+
2092+
ExtractedTypeTraitInfo TraitInfo = ExtractTypeTraitFromExpression(E);
2093+
if (!TraitInfo)
2094+
return;
2095+
2096+
const auto &[Trait, Args] = TraitInfo.value();
2097+
switch (Trait) {
2098+
case UTT_IsCppTriviallyRelocatable:
2099+
DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]);
2100+
break;
2101+
default:
2102+
break;
2103+
}
2104+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD1 %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD2 %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -DSTD3 %s
4+
5+
namespace std {
6+
7+
#ifdef STD1
8+
template <typename T>
9+
struct is_trivially_relocatable {
10+
static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T);
11+
};
12+
13+
template <typename T>
14+
constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T);
15+
#endif
16+
17+
#ifdef STD2
18+
template <typename T>
19+
struct __details_is_trivially_relocatable {
20+
static constexpr bool value = __builtin_is_cpp_trivially_relocatable(T);
21+
};
22+
23+
template <typename T>
24+
using is_trivially_relocatable = __details_is_trivially_relocatable<T>;
25+
26+
template <typename T>
27+
constexpr bool is_trivially_relocatable_v = __builtin_is_cpp_trivially_relocatable(T);
28+
#endif
29+
30+
31+
#ifdef STD3
32+
template< class T, T v >
33+
struct integral_constant {
34+
static constexpr T value = v;
35+
};
36+
37+
template< bool B >
38+
using bool_constant = integral_constant<bool, B>;
39+
40+
template <typename T>
41+
struct __details_is_trivially_relocatable : bool_constant<__builtin_is_cpp_trivially_relocatable(T)> {};
42+
43+
template <typename T>
44+
using is_trivially_relocatable = __details_is_trivially_relocatable<T>;
45+
46+
template <typename T>
47+
constexpr bool is_trivially_relocatable_v = is_trivially_relocatable<T>::value;
48+
#endif
49+
50+
}
51+
52+
static_assert(std::is_trivially_relocatable<int>::value);
53+
54+
static_assert(std::is_trivially_relocatable<int&>::value);
55+
// expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_trivially_relocatable<int &>::value'}} \
56+
// expected-note@-1 {{'int &' is not trivially relocatable}} \
57+
// expected-note@-1 {{because it is a reference type}}
58+
static_assert(std::is_trivially_relocatable_v<int&>);
59+
// expected-error@-1 {{static assertion failed due to requirement 'std::is_trivially_relocatable_v<int &>'}} \
60+
// expected-note@-1 {{'int &' is not trivially relocatable}} \
61+
// expected-note@-1 {{because it is a reference type}}
62+
63+
namespace test_namespace {
64+
using namespace std;
65+
static_assert(is_trivially_relocatable<int&>::value);
66+
// expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_trivially_relocatable<int &>::value'}} \
67+
// expected-note@-1 {{'int &' is not trivially relocatable}} \
68+
// expected-note@-1 {{because it is a reference type}}
69+
static_assert(is_trivially_relocatable_v<int&>);
70+
// expected-error@-1 {{static assertion failed due to requirement 'is_trivially_relocatable_v<int &>'}} \
71+
// expected-note@-1 {{'int &' is not trivially relocatable}} \
72+
// expected-note@-1 {{because it is a reference type}}
73+
}
74+
75+
76+
namespace concepts {
77+
template <typename T>
78+
requires std::is_trivially_relocatable<T>::value void f(); // #cand1
79+
80+
template <typename T>
81+
concept C = std::is_trivially_relocatable_v<T>; // #concept2
82+
83+
template <C T> void g(); // #cand2
84+
85+
void test() {
86+
f<int&>();
87+
// expected-error@-1 {{no matching function for call to 'f'}} \
88+
// expected-note@#cand1 {{candidate template ignored: constraints not satisfied [with T = int &]}} \
89+
// expected-note-re@#cand1 {{because '{{.*}}is_trivially_relocatable<int &>::value' evaluated to false}} \
90+
// expected-note@#cand1 {{'int &' is not trivially relocatable}} \
91+
// expected-note@#cand1 {{because it is a reference type}}
92+
93+
g<int&>();
94+
// expected-error@-1 {{no matching function for call to 'g'}} \
95+
// expected-note@#cand2 {{candidate template ignored: constraints not satisfied [with T = int &]}} \
96+
// expected-note@#cand2 {{because 'int &' does not satisfy 'C'}} \
97+
// expected-note@#concept2 {{because 'std::is_trivially_relocatable_v<int &>' evaluated to false}} \
98+
// expected-note@#concept2 {{'int &' is not trivially relocatable}} \
99+
// expected-note@#concept2 {{because it is a reference type}}
100+
}
101+
}

0 commit comments

Comments
 (0)