Skip to content

Commit 19ed57f

Browse files
committed
[Clang] Explain why a type trait evaluated to false.
`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 9502d1b commit 19ed57f

File tree

8 files changed

+471
-1
lines changed

8 files changed

+471
-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: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1917,3 +1917,190 @@ 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+
if (!BaseDecl)
1995+
continue;
1996+
if (B.isVirtual())
1997+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
1998+
<< diag::TraitNotSatisfiedReason::VBase << B.getType()
1999+
<< B.getSourceRange();
2000+
if (!SemaRef.IsCXXTriviallyRelocatableType(B.getType()))
2001+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2002+
<< diag::TraitNotSatisfiedReason::NRBase << B.getType()
2003+
<< B.getSourceRange();
2004+
}
2005+
for (const FieldDecl *Field : D->fields()) {
2006+
if (Field->getType()->isReferenceType())
2007+
continue;
2008+
if (!SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
2009+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2010+
<< diag::TraitNotSatisfiedReason::NRField << Field << Field->getType()
2011+
<< Field->getSourceRange();
2012+
}
2013+
if (D->hasDeletedDestructor())
2014+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2015+
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*Deleted*/ 0
2016+
<< D->getDestructor()->getSourceRange();
2017+
2018+
if (D->hasAttr<TriviallyRelocatableAttr>())
2019+
return;
2020+
2021+
if (D->isUnion()) {
2022+
auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) {
2023+
if (Has)
2024+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2025+
<< diag::TraitNotSatisfiedReason::UnionWithUserDeclaredSMF << K;
2026+
};
2027+
DiagSPM(CXXSpecialMemberKind::CopyConstructor,
2028+
D->hasUserDeclaredCopyConstructor());
2029+
DiagSPM(CXXSpecialMemberKind::CopyAssignment,
2030+
D->hasUserDeclaredCopyAssignment());
2031+
DiagSPM(CXXSpecialMemberKind::MoveConstructor,
2032+
D->hasUserDeclaredMoveConstructor());
2033+
DiagSPM(CXXSpecialMemberKind::MoveAssignment,
2034+
D->hasUserDeclaredMoveAssignment());
2035+
return;
2036+
}
2037+
2038+
if (!D->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) {
2039+
const auto *Decl = cast<CXXConstructorDecl>(
2040+
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false));
2041+
if (Decl && Decl->isUserProvided())
2042+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2043+
<< diag::TraitNotSatisfiedReason::UserProvidedCtr
2044+
<< Decl->isMoveConstructor() << Decl->getSourceRange();
2045+
}
2046+
if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) {
2047+
CXXMethodDecl *Decl =
2048+
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true);
2049+
if (Decl && Decl->isUserProvided())
2050+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2051+
<< diag::TraitNotSatisfiedReason::UserProvidedAssign
2052+
<< Decl->isMoveAssignmentOperator() << Decl->getSourceRange();
2053+
}
2054+
CXXDestructorDecl *Dtr = D->getDestructor();
2055+
if (Dtr && Dtr->isUserProvided() && !Dtr->isDefaulted())
2056+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2057+
<< diag::TraitNotSatisfiedReason::DeletedDtr << /*User Provided*/ 1
2058+
<< Dtr->getSourceRange();
2059+
}
2060+
2061+
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
2062+
SourceLocation Loc,
2063+
QualType T) {
2064+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
2065+
<< T << diag::TraitName::TriviallyRelocatable;
2066+
if (T->isVariablyModifiedType())
2067+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2068+
<< diag::TraitNotSatisfiedReason::VLA;
2069+
2070+
if (T->isReferenceType())
2071+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2072+
<< diag::TraitNotSatisfiedReason::Ref;
2073+
T = T.getNonReferenceType();
2074+
2075+
if (T.hasNonTrivialObjCLifetime())
2076+
SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
2077+
<< diag::TraitNotSatisfiedReason::HasArcLifetime;
2078+
2079+
const CXXRecordDecl *D = T->getAsCXXRecordDecl();
2080+
if (!D)
2081+
return;
2082+
2083+
if (D->hasDefinition())
2084+
DiagnoseNonTriviallyRelocatableReason(SemaRef, Loc, D);
2085+
2086+
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
2087+
}
2088+
2089+
void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
2090+
E = E->IgnoreParenImpCasts();
2091+
if (E->containsErrors())
2092+
return;
2093+
2094+
ExtractedTypeTraitInfo TraitInfo = ExtractTypeTraitFromExpression(E);
2095+
if (!TraitInfo)
2096+
return;
2097+
2098+
const auto &[Trait, Args] = TraitInfo.value();
2099+
switch (Trait) {
2100+
case UTT_IsCppTriviallyRelocatable:
2101+
DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]);
2102+
break;
2103+
default:
2104+
break;
2105+
}
2106+
}
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)