-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[Clang] Explain why a type is not replaceable. #143265
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
Conversation
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3
@llvm/pr-subscribers-clang Author: Corentin Jabot (cor3ntin) ChangesAs 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:
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@-1 {{static assertion failed due to requirement 'std::is_replaceable<int &>::value'}} \
+// expected-note@-1 {{'int &' is not replaceable}} \
+// expected-note@-1 {{because it is a reference type}}
+static_assert(std::is_replaceable_v<int&>);
+// expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \
+// expected-note@-1 {{'int &' is not replaceable}} \
+// expected-note@-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@-1 {{because it has a deleted destructor}} \
// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(int[vla_size])'}} \
+// expected-note@-1 {{'int[vla_size]' is not replaceable}} \
+// expected-note@-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@-1 {{incomplete type 'S' used in type trait expression}}
+
+static_assert(__builtin_is_replaceable(const volatile int));
+// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(const volatile int)}} \
+// expected-note@-1 {{'const volatile int' is not replaceable}} \
+// expected-note@-1 {{because it is const}} \
+// expected-note@-1 {{because it is volatile}}
+
+
+static_assert(__builtin_is_replaceable(void()));
+// expected-error@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(void ())}} \
+// expected-note@-1 {{'void ()' is not replaceable}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S)'}} \
+// expected-note@-1 {{'S' is not replaceable}} \
+// expected-note@-1 {{because it has a non-replaceable base 'B'}} \
+// expected-note@-1 {{because it has a non-replaceable member 'a' of type 'int &'}} \
+// expected-note@-1 {{because it has a non-replaceable member 'ci' of type 'const int'}} \
+// expected-note@-1 {{because it has a non-replaceable member 'b' of type 'B &'}} \
+// expected-note@-1 {{because it has a non-replaceable member 'c' of type 'B'}} \
+// expected-note@-1 {{because it has a user-provided destructor}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S2)'}} \
+// expected-note@-1 {{'S2' is not replaceable}} \
+// expected-note@-1 {{because it has a user provided move constructor}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S3)'}} \
+// expected-note@-1 {{'S3' is not replaceable}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U)'}} \
+// expected-note@-1 {{'U' is not replaceable}} \
+// expected-note@-1 {{because it is a union with a user-declared copy constructor}} \
+// expected-note@-1 {{because it is a union with a user-declared copy assignment operator}} \
+// expected-note@-1 {{because it is a union with a user-declared move constructor}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::S4)'}} \
+// expected-note@-1 {{'S4' is not replaceable}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::U2)'}} \
+// expected-note@-1 {{'U2' is not replaceable}} \
+// expected-note@-1 {{because it has a deleted destructor}} \
+// expected-note@-1 {{because it has a non-replaceable member 'b' of type 'B'}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD1)'}} \
+// expected-note@-1 {{'UD1' is not replaceable}} \
+// expected-note@-1 {{because it has a deleted copy constructor}} \
+// expected-note@-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@-1 {{static assertion failed due to requirement '__builtin_is_replaceable(replaceable::UD2)'}} \
+// expected-note@-1 {{'UD2' is not replaceable}} \
+// expected-note@-1 {{because it has a deleted move constructor}} \
+// expected-note@-1 {{because it has a deleted move assignment...
[truncated]
|
"%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 " |
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.
I don't see a test for the an array of objects
case
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3
Adjustment to llvm#143265; `because it not` should be `because it is not`.
Adjustment to #143265; `because it not` should be `because it is not`.
As a drive by fix the definition of replaceable, that did not correctly implement https://eel.is/c++draft/class.prop#6.3