[clang] [Clang] Explain why a type is not replaceable. (PR #143265)
via cfe-commits
cfe-commits at lists.llvm.org
Sat Jun 7 06:19:00 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Corentin Jabot (cor3ntin)
<details>
<summary>Changes</summary>
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#<!-- -->6.3
---
Patch is 20.08 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/143265.diff
5 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+16-4)
- (modified) clang/lib/Sema/SemaTypeTraits.cpp (+128-31)
- (modified) clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp (+25)
- (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp (+21)
- (modified) clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp (+131)
``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 5f44d503580b9..f77ddbcff8ec0 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -1763,27 +1763,39 @@ def err_user_defined_msg_constexpr : Error<
"constant expression">;
// Type traits explanations
-def note_unsatisfied_trait : Note<"%0 is not %enum_select<TraitName>{"
- "%TriviallyRelocatable{trivially relocatable}|"
- "%TriviallyCopyable{trivially copyable}"
- "}1">;
+def note_unsatisfied_trait
+ : Note<"%0 is not %enum_select<TraitName>{"
+ "%TriviallyRelocatable{trivially relocatable}|"
+ "%Replaceable{replaceable}|"
+ "%TriviallyCopyable{trivially copyable}"
+ "}1">;
def note_unsatisfied_trait_reason
: Note<"because it "
"%enum_select<TraitNotSatisfiedReason>{"
"%Ref{is a reference type}|"
+ "%Const{is const}|"
+ "%Volatile{is volatile}|"
"%HasArcLifetime{has an ARC lifetime qualifier}|"
"%VLA{is a variably-modified type}|"
"%VBase{has a virtual base %1}|"
+ "%NotScalarOrClass{not %select{a|an array of objects of}1 scalar or "
+ "class type}|"
"%NTRBase{has a non-trivially-relocatable base %1}|"
"%NTRField{has a non-trivially-relocatable member %1 of type %2}|"
+ "%NonReplaceableBase{has a non-replaceable base %1}|"
+ "%NonReplaceableField{has a non-replaceable member %1 of type %2}|"
"%NTCBase{has a non-trivially-copyable base %1}|"
"%NTCField{has a non-trivially-copyable member %1 of type %2}|"
"%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|"
"%UserProvidedCtr{has a user provided %select{copy|move}1 "
"constructor}|"
+ "%DeletedCtr{has a deleted %select{copy|move}1 "
+ "constructor}|"
"%UserProvidedAssign{has a user provided %select{copy|move}1 "
"assignment operator}|"
+ "%DeletedAssign{has a deleted %select{copy|move}1 "
+ "assignment operator}|"
"%UnionWithUserDeclaredSMF{is a union with a user-declared "
"%sub{select_special_member_kind}1}"
"}0">;
diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp
index 330f2aa750a09..eba4dbb457ae6 100644
--- a/clang/lib/Sema/SemaTypeTraits.cpp
+++ b/clang/lib/Sema/SemaTypeTraits.cpp
@@ -104,6 +104,7 @@ static CXXMethodDecl *LookupSpecialMemberFromXValue(Sema &SemaRef,
OverloadCandidateSet::iterator Best;
switch (OCS.BestViableFunction(SemaRef, LookupLoc, Best)) {
case OR_Success:
+ case OR_Deleted:
return cast<CXXMethodDecl>(Best->Function);
default:
return nullptr;
@@ -120,7 +121,8 @@ static bool hasSuitableConstructorForRelocation(Sema &SemaRef,
CXXMethodDecl *Decl =
LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false);
- return Decl && Decl->isUserProvided() == AllowUserDefined;
+ return Decl && Decl->isUserProvided() == AllowUserDefined &&
+ !Decl->isDeleted();
}
static bool hasSuitableMoveAssignmentOperatorForRelocation(
@@ -135,7 +137,8 @@ static bool hasSuitableMoveAssignmentOperatorForRelocation(
if (!Decl)
return false;
- return Decl && Decl->isUserProvided() == AllowUserDefined;
+ return Decl && Decl->isUserProvided() == AllowUserDefined &&
+ !Decl->isDeleted();
}
// [C++26][class.prop]
@@ -1940,6 +1943,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) {
return llvm::StringSwitch<std::optional<TypeTrait>>(Name)
.Case("is_trivially_relocatable",
TypeTrait::UTT_IsCppTriviallyRelocatable)
+ .Case("is_replaceable", TypeTrait::UTT_IsReplaceable)
.Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable)
.Default(std::nullopt);
}
@@ -2005,35 +2009,8 @@ static ExtractedTypeTraitInfo ExtractTypeTraitFromExpression(const Expr *E) {
return std::nullopt;
}
-static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
- SourceLocation Loc,
- const CXXRecordDecl *D) {
- for (const CXXBaseSpecifier &B : D->bases()) {
- assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
- 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::NTRBase << B.getType()
- << B.getSourceRange();
- }
- for (const FieldDecl *Field : D->fields()) {
- if (!Field->getType()->isReferenceType() &&
- !SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
- SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
- << diag::TraitNotSatisfiedReason::NTRField << 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;
-
+static void DiagnoseNonDefaultMovable(Sema &SemaRef, SourceLocation Loc,
+ const CXXRecordDecl *D) {
if (D->isUnion()) {
auto DiagSPM = [&](CXXSpecialMemberKind K, bool Has) {
if (Has)
@@ -2074,6 +2051,37 @@ static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
<< Dtr->getSourceRange();
}
+static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
+ SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ for (const CXXBaseSpecifier &B : D->bases()) {
+ assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
+ 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::NTRBase << B.getType()
+ << B.getSourceRange();
+ }
+ for (const FieldDecl *Field : D->fields()) {
+ if (!Field->getType()->isReferenceType() &&
+ !SemaRef.IsCXXTriviallyRelocatableType(Field->getType()))
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NTRField << 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;
+ DiagnoseNonDefaultMovable(SemaRef, Loc, D);
+}
+
static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
SourceLocation Loc,
QualType T) {
@@ -2102,6 +2110,92 @@ static void DiagnoseNonTriviallyRelocatableReason(Sema &SemaRef,
SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
}
+static void DiagnoseNonReplaceableReason(Sema &SemaRef, SourceLocation Loc,
+ const CXXRecordDecl *D) {
+ for (const CXXBaseSpecifier &B : D->bases()) {
+ assert(B.getType()->getAsCXXRecordDecl() && "invalid base?");
+ if (!SemaRef.IsCXXReplaceableType(B.getType()))
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonReplaceableBase << B.getType()
+ << B.getSourceRange();
+ }
+ for (const FieldDecl *Field : D->fields()) {
+ if (!SemaRef.IsCXXReplaceableType(Field->getType()))
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NonReplaceableField << 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->hasSimpleMoveConstructor() && !D->hasSimpleCopyConstructor()) {
+ const auto *Decl = cast<CXXConstructorDecl>(
+ LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/false));
+ if (Decl && Decl->isDeleted())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::DeletedCtr
+ << Decl->isMoveConstructor() << Decl->getSourceRange();
+ }
+ if (!D->hasSimpleMoveAssignment() && !D->hasSimpleCopyAssignment()) {
+ CXXMethodDecl *Decl =
+ LookupSpecialMemberFromXValue(SemaRef, D, /*Assign=*/true);
+ if (Decl && Decl->isDeleted())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::DeletedAssign
+ << Decl->isMoveAssignmentOperator() << Decl->getSourceRange();
+ }
+
+ if (D->hasAttr<ReplaceableAttr>())
+ return;
+ DiagnoseNonDefaultMovable(SemaRef, Loc, D);
+}
+
+static void DiagnoseNonReplaceableReason(Sema &SemaRef, SourceLocation Loc,
+ QualType T) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait)
+ << T << diag::TraitName::Replaceable;
+
+ 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.isConstQualified())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Const;
+
+ if (T.isVolatileQualified())
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::Volatile;
+
+ bool IsArray = T->isArrayType();
+ T = SemaRef.getASTContext().getBaseElementType(T.getUnqualifiedType());
+
+ if (T->isScalarType())
+ return;
+
+ const CXXRecordDecl *D = T->getAsCXXRecordDecl();
+ if (!D) {
+ SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason)
+ << diag::TraitNotSatisfiedReason::NotScalarOrClass << IsArray;
+ return;
+ }
+
+ if (D->isInvalidDecl())
+ return;
+
+ if (D->hasDefinition())
+ DiagnoseNonReplaceableReason(SemaRef, Loc, D);
+
+ SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D;
+}
+
static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef,
SourceLocation Loc,
const CXXRecordDecl *D) {
@@ -2192,6 +2286,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) {
case UTT_IsCppTriviallyRelocatable:
DiagnoseNonTriviallyRelocatableReason(*this, E->getBeginLoc(), Args[0]);
break;
+ case UTT_IsReplaceable:
+ DiagnoseNonReplaceableReason(*this, E->getBeginLoc(), Args[0]);
+ break;
case UTT_IsTriviallyCopyable:
DiagnoseNonTriviallyCopyableReason(*this, E->getBeginLoc(), Args[0]);
break;
diff --git a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
index 062becd4f5776..aff172e0bc70a 100644
--- a/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
+++ b/clang/test/SemaCXX/cxx2c-trivially-relocatable.cpp
@@ -333,6 +333,31 @@ struct CopyAssign1 {
CopyAssign1 & operator=(CopyAssign1 const &) = default;
};
+struct UserDeleted1 {
+ UserDeleted1(const UserDeleted1&) = delete;
+};
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted1));
+static_assert(!__builtin_is_replaceable(UserDeleted1));
+
+struct UserDeleted2 {
+ UserDeleted2(UserDeleted2&&) = delete;
+};
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted2));
+static_assert(!__builtin_is_replaceable(UserDeleted2));
+
+
+struct UserDeleted3 {
+ UserDeleted3 operator=(UserDeleted3);
+};
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted3));
+static_assert(!__builtin_is_replaceable(UserDeleted3));
+
+struct UserDeleted4 {
+ UserDeleted4 operator=(UserDeleted4&&);
+};
+static_assert(!__builtin_is_cpp_trivially_relocatable(UserDeleted4));
+static_assert(!__builtin_is_replaceable(UserDeleted4));
+
}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
index 498e202e26265..329b611110c1d 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp
@@ -171,3 +171,24 @@ void test() {
// expected-note@#concept4 {{because it is a reference type}}
}
}
+
+
+namespace std {
+template <typename T>
+struct is_replaceable {
+ static constexpr bool value = __builtin_is_replaceable(T);
+};
+
+template <typename T>
+constexpr bool is_replaceable_v = __builtin_is_replaceable(T);
+
+}
+
+static_assert(std::is_replaceable<int&>::value);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_replaceable<int &>::value'}} \
+// expected-note at -1 {{'int &' is not replaceable}} \
+// expected-note at -1 {{because it is a reference type}}
+static_assert(std::is_replaceable_v<int&>);
+// expected-error at -1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \
+// expected-note at -1 {{'int &' is not replaceable}} \
+// expected-note at -1 {{because it is a reference type}}
diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
index 0256569fcca5f..0fa3b86a0d214 100644
--- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
+++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp
@@ -142,6 +142,137 @@ static_assert(__builtin_is_cpp_trivially_relocatable(U2));
// expected-note at -1 {{because it has a deleted destructor}} \
// expected-note at -1 {{because it has a non-trivially-relocatable member 'b' of type 'B'}} \
// expected-note@#tr-U2 {{'U2' defined here}}
+}
+
+namespace replaceable {
+
+extern int vla_size;
+static_assert(__builtin_is_replaceable(int[vla_size]));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(int[vla_size])'}} \
+// expected-note at -1 {{'int[vla_size]' is not replaceable}} \
+// expected-note at -1 {{because it is a variably-modified type}}
+
+struct S; // expected-note {{forward declaration of 'replaceable::S'}}
+static_assert(__builtin_is_replaceable(S));
+// expected-error at -1 {{incomplete type 'S' used in type trait expression}}
+
+static_assert(__builtin_is_replaceable(const volatile int));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(const volatile int)}} \
+// expected-note at -1 {{'const volatile int' is not replaceable}} \
+// expected-note at -1 {{because it is const}} \
+// expected-note at -1 {{because it is volatile}}
+
+
+static_assert(__builtin_is_replaceable(void()));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(void ())}} \
+// expected-note at -1 {{'void ()' is not replaceable}} \
+// expected-note at -1 {{because it not a scalar or class type}}
+
+struct B {
+ virtual ~B();
+};
+struct S : virtual B { // #replaceable-S
+ S();
+ int & a;
+ const int ci;
+ B & b;
+ B c;
+ ~S();
+};
+static_assert(__builtin_is_replaceable(S));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S)'}} \
+// expected-note at -1 {{'S' is not replaceable}} \
+// expected-note at -1 {{because it has a non-replaceable base 'B'}} \
+// expected-note at -1 {{because it has a non-replaceable member 'a' of type 'int &'}} \
+// expected-note at -1 {{because it has a non-replaceable member 'ci' of type 'const int'}} \
+// expected-note at -1 {{because it has a non-replaceable member 'b' of type 'B &'}} \
+// expected-note at -1 {{because it has a non-replaceable member 'c' of type 'B'}} \
+// expected-note at -1 {{because it has a user-provided destructor}} \
+// expected-note at -1 {{because it has a deleted copy assignment operator}}
+// expected-note@#replaceable-S {{'S' defined here}}
+
+struct S2 { // #replaceable-S2
+ S2(S2&&);
+ S2& operator=(const S2&);
+};
+static_assert(__builtin_is_replaceable(S2));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S2)'}} \
+// expected-note at -1 {{'S2' is not replaceable}} \
+// expected-note at -1 {{because it has a user provided move constructor}} \
+// expected-note at -1 {{because it has a user provided copy assignment operator}} \
+// expected-note@#replaceable-S2 {{'S2' defined here}}
+
+
+struct S3 { // #replaceable-S3
+ ~S3() = delete;
+};
+static_assert(__builtin_is_replaceable(S3));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S3)'}} \
+// expected-note at -1 {{'S3' is not replaceable}} \
+// expected-note at -1 {{because it has a deleted destructor}} \
+// expected-note@#replaceable-S3 {{'S3' defined here}}
+
+
+union U { // #replaceable-U
+ U(const U&);
+ U(U&&);
+ U& operator=(const U&);
+ U& operator=(U&&);
+};
+static_assert(__builtin_is_replaceable(U));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U)'}} \
+// expected-note at -1 {{'U' is not replaceable}} \
+// expected-note at -1 {{because it is a union with a user-declared copy constructor}} \
+// expected-note at -1 {{because it is a union with a user-declared copy assignment operator}} \
+// expected-note at -1 {{because it is a union with a user-declared move constructor}} \
+// expected-note at -1 {{because it is a union with a user-declared move assignment operator}}
+// expected-note@#replaceable-U {{'U' defined here}}
+struct S4 replaceable_if_eligible { // #replaceable-S4
+ ~S4();
+ B b;
+};
+static_assert(__builtin_is_replaceable(S4));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S4)'}} \
+// expected-note at -1 {{'S4' is not replaceable}} \
+// expected-note at -1 {{because it has a non-replaceable member 'b' of type 'B'}} \
+// expected-note@#replaceable-S4 {{'S4' defined here}}
+
+union U2 replaceable_if_eligible { // #replaceable-U2
+ U2(const U2&);
+ U2(U2&&);
+ B b;
+};
+static_assert(__builtin_is_replaceable(U2));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U2)'}} \
+// expected-note at -1 {{'U2' is not replaceable}} \
+// expected-note at -1 {{because it has a deleted destructor}} \
+// expected-note at -1 {{because it has a non-replaceable member 'b' of type 'B'}} \
+// expected-note at -1 {{because it has a deleted copy assignment operator}} \
+// expected-note@#replaceable-U2 {{'U2' defined here}}
+
+struct UD1 { // #replaceable-UD1
+ UD1(const UD1&) = delete;
+ UD1 & operator=(const UD1&) = delete;
+
+};
+static_assert(__builtin_is_replaceable(UD1));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD1)'}} \
+// expected-note at -1 {{'UD1' is not replaceable}} \
+// expected-note at -1 {{because it has a deleted copy constructor}} \
+// expected-note at -1 {{because it has a deleted copy assignment operator}} \
+// expected-note@#replaceable-UD1 {{'UD1' defined here}}
+
+
+struct UD2 { // #replaceable-UD2
+ UD2(UD2&&) = delete;
+ UD2 & operator=(UD2&&) = delete;
+};
+static_assert(__builtin_is_replaceable(UD2));
+// expected-error at -1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD2)'}} \
+// expected-note at -1 {{'UD2' is not replaceable}} \
+// expected-note at -1 {{because it has a deleted move constructor}} \
+// expected-note at -1 {{because it has a deleted move assignment...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/143265
More information about the cfe-commits
mailing list