[clang] [clang-tools-extra] [Clang] Implement CWG2813: Class member access with prvalues (PR #95112)
Mital Ashok via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 17 12:43:16 PDT 2024
https://github.com/MitalAshok updated https://github.com/llvm/llvm-project/pull/95112
>From e53dfbc9b2c6b7f30c1378731d7de284fa99d568 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 11 Jun 2024 14:26:38 +0100
Subject: [PATCH 1/6] [Clang] Implement CWG2813
---
clang/docs/ReleaseNotes.rst | 5 ++
.../clang/Basic/DiagnosticSemaKinds.td | 3 +
clang/lib/Sema/SemaExprMember.cpp | 64 +++++++++++---
clang/lib/Sema/SemaOverload.cpp | 11 +--
clang/lib/Sema/SemaStmt.cpp | 14 ++-
.../test/AST/ast-dump-for-range-lifetime.cpp | 12 +--
.../dcl.attr/dcl.attr.nodiscard/p2.cpp | 86 ++++++++++++++-----
clang/test/CXX/drs/cwg28xx.cpp | 17 ++++
clang/test/CodeGenCXX/cxx2b-deducing-this.cpp | 1 -
clang/www/cxx_dr_status.html | 2 +-
10 files changed, 164 insertions(+), 51 deletions(-)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cf1ba02cbc4b2..36bf1fdea3602 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -260,6 +260,11 @@ Resolutions to C++ Defect Reports
- Clang now requires a template argument list after a template keyword.
(`CWG96: Syntactic disambiguation using the template keyword <https://cplusplus.github.io/CWG/issues/96.html>`_).
+- Clang now allows calling explicit object member functions directly with prvalues
+ instead of always materializing a temporary, meaning by-value explicit object parameters
+ do not need to move from a temporary.
+ (`CWG2813: Class member access with prvalues <https://cplusplus.github.io/CWG/issues/2813.html>`_).
+
C Language Changes
------------------
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 193eae3bc41d6..008bf5fa0ccfc 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9182,6 +9182,9 @@ def warn_unused_constructor : Warning<
def warn_unused_constructor_msg : Warning<
"ignoring temporary created by a constructor declared with %0 attribute: %1">,
InGroup<UnusedValue>;
+def warn_discarded_class_member_access : Warning<
+ "left operand of dot in this class member access is discarded and has no effect">,
+ InGroup<UnusedValue>;
def warn_side_effects_unevaluated_context : Warning<
"expression with side effects has no effect in an unevaluated context">,
InGroup<UnevaluatedExpression>;
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 3ae1af26d0096..4679fe529ac91 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1015,15 +1015,6 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
: !isDependentScopeSpecifier(SS) || computeDeclContext(SS)) &&
"dependent lookup context that isn't the current instantiation?");
- // C++1z [expr.ref]p2:
- // For the first option (dot) the first expression shall be a glvalue [...]
- if (!IsArrow && BaseExpr && BaseExpr->isPRValue()) {
- ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
- if (Converted.isInvalid())
- return ExprError();
- BaseExpr = Converted.get();
- }
-
const DeclarationNameInfo &MemberNameInfo = R.getLookupNameInfo();
DeclarationName MemberName = MemberNameInfo.getName();
SourceLocation MemberLoc = MemberNameInfo.getLoc();
@@ -1140,26 +1131,68 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
BaseExpr = BuildCXXThisExpr(Loc, BaseExprType, /*IsImplicit=*/true);
}
+ // C++17 [expr.ref]p2, per CWG2813:
+ // For the first option (dot), if the id-expression names a static member or
+ // an enumerator, the first expression is a discarded-value expression; if
+ // the id-expression names a non-static data member, the first expression
+ // shall be a glvalue.
+ auto MakeDiscardedValue = [&BaseExpr, IsArrow, this] {
+ assert(getLangOpts().CPlusPlus &&
+ "Static member / member enumerator outside of C++");
+ if (IsArrow)
+ return false;
+ ExprResult Converted = IgnoredValueConversions(BaseExpr);
+ if (Converted.isInvalid())
+ return true;
+ BaseExpr = Converted.get();
+ DiagnoseUnusedExprResult(BaseExpr,
+ diag::warn_discarded_class_member_access);
+ return false;
+ };
+ auto MakeGLValue = [&BaseExpr, IsArrow, this] {
+ if (IsArrow || !BaseExpr->isPRValue())
+ return false;
+ ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
+ if (Converted.isInvalid())
+ return true;
+ BaseExpr = Converted.get();
+ return false;
+ };
+
// Check the use of this member.
if (DiagnoseUseOfDecl(MemberDecl, MemberLoc))
return ExprError();
- if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl))
+ if (FieldDecl *FD = dyn_cast<FieldDecl>(MemberDecl)) {
+ if (MakeGLValue())
+ return ExprError();
return BuildFieldReferenceExpr(BaseExpr, IsArrow, OpLoc, SS, FD, FoundDecl,
MemberNameInfo);
+ }
- if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl))
+ if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl)) {
+ // Properties treated as non-static data members for the purpose of
+ // temporary materialization
+ if (MakeGLValue())
+ return ExprError();
return BuildMSPropertyRefExpr(*this, BaseExpr, IsArrow, SS, PD,
MemberNameInfo);
+ }
- if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl))
+ if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl)) {
+ if (MakeGLValue())
+ return ExprError();
// We may have found a field within an anonymous union or struct
// (C++ [class.union]).
return BuildAnonymousStructUnionMemberReference(SS, MemberLoc, FD,
FoundDecl, BaseExpr,
OpLoc);
+ }
+ // Static data member
if (VarDecl *Var = dyn_cast<VarDecl>(MemberDecl)) {
+ if (MakeDiscardedValue())
+ return ExprError();
return BuildMemberExpr(BaseExpr, IsArrow, OpLoc,
SS.getWithLocInContext(Context), TemplateKWLoc, Var,
FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1174,6 +1207,9 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
valueKind = VK_PRValue;
type = Context.BoundMemberTy;
} else {
+ // Static member function
+ if (MakeDiscardedValue())
+ return ExprError();
valueKind = VK_LValue;
type = MemberFn->getType();
}
@@ -1186,6 +1222,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
assert(!isa<FunctionDecl>(MemberDecl) && "member function not C++ method?");
if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
+ if (MakeDiscardedValue())
+ return ExprError();
return BuildMemberExpr(
BaseExpr, IsArrow, OpLoc, SS.getWithLocInContext(Context),
TemplateKWLoc, Enum, FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1193,6 +1231,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
}
if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
+ if (MakeDiscardedValue())
+ return ExprError();
if (!TemplateArgs) {
diagnoseMissingTemplateArguments(
SS, /*TemplateKeyword=*/TemplateKWLoc.isValid(), VarTempl, MemberLoc);
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 1b4bcdcb51160..3aa87dae54dd8 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5922,7 +5922,9 @@ ExprResult Sema::PerformImplicitObjectArgumentInitialization(
DestType = ImplicitParamRecordType;
FromClassification = From->Classify(Context);
- // When performing member access on a prvalue, materialize a temporary.
+ // CWG2813 [expr.call]p6:
+ // If the function is an implicit object member function, the object
+ // expression of the class member access shall be a glvalue [...]
if (From->isPRValue()) {
From = CreateMaterializeTemporaryExpr(FromRecordType, From,
Method->getRefQualifier() !=
@@ -6457,11 +6459,6 @@ static Expr *GetExplicitObjectExpr(Sema &S, Expr *Obj,
VK_LValue, OK_Ordinary, SourceLocation(),
/*CanOverflow=*/false, FPOptionsOverride());
}
- if (Obj->Classify(S.getASTContext()).isPRValue()) {
- Obj = S.CreateMaterializeTemporaryExpr(
- ObjType, Obj,
- !Fun->getParamDecl(0)->getType()->isRValueReferenceType());
- }
return Obj;
}
@@ -15709,8 +15706,6 @@ ExprResult Sema::BuildCallToMemberFunction(Scope *S, Expr *MemExprE,
CurFPFeatureOverrides(), Proto->getNumParams());
} else {
// Convert the object argument (for a non-static member function call).
- // We only need to do this if there was actually an overload; otherwise
- // it was done at lookup.
ExprResult ObjectArg = PerformImplicitObjectArgumentInitialization(
MemExpr->getBase(), Qualifier, FoundDecl, Method);
if (ObjectArg.isInvalid())
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 57465d4a77ac2..7effaa943a226 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -223,6 +223,7 @@ static bool DiagnoseNoDiscard(Sema &S, const WarnUnusedResultAttr *A,
}
void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
+ const unsigned OrigDiagID = DiagID;
if (const LabelStmt *Label = dyn_cast_or_null<LabelStmt>(S))
return DiagnoseUnusedExprResult(Label->getSubStmt(), DiagID);
@@ -387,9 +388,16 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
// Do not diagnose use of a comma operator in a SFINAE context because the
// type of the left operand could be used for SFINAE, so technically it is
// *used*.
- if (DiagID != diag::warn_unused_comma_left_operand || !isSFINAEContext())
- DiagIfReachable(Loc, S ? llvm::ArrayRef(S) : std::nullopt,
- PDiag(DiagID) << R1 << R2);
+ if (DiagID == diag::warn_unused_comma_left_operand && isSFINAEContext())
+ return;
+
+ // Don't diagnose discarded left of dot in static class member access
+ // because its type is "used" to determine the class to access
+ if (OrigDiagID == diag::warn_discarded_class_member_access)
+ return;
+
+ DiagIfReachable(Loc, S ? llvm::ArrayRef(S) : std::nullopt,
+ PDiag(DiagID) << R1 << R2);
}
void Sema::ActOnStartOfCompoundStmt(bool IsStmtExpr) {
diff --git a/clang/test/AST/ast-dump-for-range-lifetime.cpp b/clang/test/AST/ast-dump-for-range-lifetime.cpp
index 0e92b6990ed50..d66e2c090791e 100644
--- a/clang/test/AST/ast-dump-for-range-lifetime.cpp
+++ b/clang/test/AST/ast-dump-for-range-lifetime.cpp
@@ -262,19 +262,19 @@ void test7() {
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A'
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A'
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CallExpr {{.*}} 'A':'P2718R0::A'
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'A (*)()' <FunctionToPointerDecay>
@@ -429,19 +429,19 @@ void test13() {
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A'
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'A':'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'A':'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A'
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .g {{.*}}
// CHECK-NEXT: | `-CXXMemberCallExpr {{.*}} 'A':'P2718R0::A' lvalue
// CHECK-NEXT: | `-MemberExpr {{.*}} '<bound member function type>' .r {{.*}}
- // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'P2718R0::A' xvalue extended by Var {{.*}} '__range1' 'A &&'
+ // CHECK-NEXT: | `-MaterializeTemporaryExpr {{.*}} 'P2718R0::A' lvalue extended by Var {{.*}} '__range1' 'A &&'
// CHECK-NEXT: | `-CXXBindTemporaryExpr {{.*}} 'P2718R0::A' (CXXTemporary {{.*}})
// CHECK-NEXT: | `-CallExpr {{.*}} 'P2718R0::A'
// CHECK-NEXT: | `-ImplicitCastExpr {{.*}} 'P2718R0::A (*)()' <FunctionToPointerDecay>
diff --git a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
index e2397c12e2e99..a088c9ab93b0e 100644
--- a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify -Wc++20-extensions %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify -Wc++17-extensions %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify -DEXT -Wc++17-extensions -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17 -Wc++17-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify=expected,cxx11-17,cxx11 -Wc++17-extensions -Wc++20-extensions %s
struct [[nodiscard]] S {};
S get_s();
@@ -124,21 +124,67 @@ void usage() {
}
}; // namespace p1771
-#ifdef EXT
-// expected-warning at 5 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 9 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 12 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 13 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 29 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 65 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 67 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 71 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 73 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 74 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 84 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 86 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 87 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 91 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// expected-warning at 92 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// expected-warning at 95 {{use of the 'nodiscard' attribute is a C++20 extension}}
+namespace discarded_member_access {
+struct X {
+ union {
+ int variant_member;
+ };
+ struct {
+ int anonymous_struct_member;
+ };
+ int data_member;
+ static int static_data_member;
+ enum {
+ unscoped_enum
+ };
+ enum class scoped_enum_t {
+ scoped_enum
+ };
+ using enum scoped_enum_t;
+ // cxx11-17-warning at -1 {{using enum declaration is a C++20 extension}}
+
+ void implicit_object_member_function();
+ static void static_member_function();
+#if __cplusplus >= 202302L
+ void explicit_object_member_function(this X self);
#endif
+};
+
+[[nodiscard]] X get_X();
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
+void f() {
+ (void) get_X().variant_member;
+ (void) get_X().anonymous_struct_member;
+ (void) get_X().data_member;
+ (void) get_X().static_data_member;
+ // expected-warning at -1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (void) get_X().unscoped_enum;
+ // expected-warning at -1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (void) get_X().scoped_enum;
+ // expected-warning at -1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+ (void) get_X().implicit_object_member_function();
+ (void) get_X().static_member_function();
+ // expected-warning at -1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+#if __cplusplus >= 202302L
+ (void) get_X().explicit_object_member_function();
+#endif
+}
+} // namespace discarded_member_access
+
+
+// cxx11-warning at 5 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 9 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 12 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 13 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 29 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 65 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 67 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 71 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 73 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 74 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 84 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 86 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 87 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 91 {{use of the 'nodiscard' attribute is a C++17 extension}}
+// cxx11-warning at 92 {{use of the 'nodiscard' attribute is a C++20 extension}}
+// cxx11-warning at 95 {{use of the 'nodiscard' attribute is a C++20 extension}}
diff --git a/clang/test/CXX/drs/cwg28xx.cpp b/clang/test/CXX/drs/cwg28xx.cpp
index da81eccc8dc22..c712240fd8120 100644
--- a/clang/test/CXX/drs/cwg28xx.cpp
+++ b/clang/test/CXX/drs/cwg28xx.cpp
@@ -6,6 +6,23 @@
// RUN: %clang_cc1 -std=c++23 -pedantic-errors -verify=expected,since-cxx20,since-cxx23 %s
// RUN: %clang_cc1 -std=c++2c -pedantic-errors -verify=expected,since-cxx20,since-cxx23,since-cxx26 %s
+namespace cwg2813 { // cwg2813: 19
+#if __cplusplus >= 202302L
+struct X {
+ X() = default;
+
+ X(const X&) = delete;
+ X& operator=(const X&) = delete;
+
+ void f(this X self) { }
+};
+
+void f() {
+ X{}.f();
+}
+#endif
+}
+
namespace cwg2819 { // cwg2819: 19 tentatively ready 2023-12-01
#if __cpp_constexpr >= 202306L
constexpr void* p = nullptr;
diff --git a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
index f9f9fbd7397f8..66f4a45cde9b6 100644
--- a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
@@ -31,7 +31,6 @@ void test_lambda() {
//CHECK: define dso_local void @{{.*}}test_lambda{{.*}}() #0 {
//CHECK: entry:
//CHECK: %agg.tmp = alloca %class.anon, align 1
-//CHECK: %ref.tmp = alloca %class.anon, align 1
//CHECK: %call = call noundef i32 @"_ZZ11test_lambdavENH3$_0clIS_EEiT_"()
//CHECK: ret void
//CHECK: }
diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index 5e2ab06701703..ee37d20345732 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -16687,7 +16687,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2>
<td><a href="https://cplusplus.github.io/CWG/issues/2813.html">2813</a></td>
<td>DR</td>
<td>Class member access with prvalues</td>
- <td class="unknown" align="center">Unknown</td>
+ <td class="unreleased" align="center">Clang 19</td>
</tr>
<tr class="open" id="2814">
<td><a href="https://cplusplus.github.io/CWG/issues/2814.html">2814</a></td>
>From edac756d0ea826f19e5c7fda2910eb286a53ca56 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Tue, 11 Jun 2024 17:07:45 +0100
Subject: [PATCH 2/6] fix clangd AST test
---
.../clangd/unittests/DumpASTTests.cpp | 41 +++++++++++++++++--
1 file changed, 37 insertions(+), 4 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
index 304682118c871..cb2c17ad4ef0d 100644
--- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
@@ -49,7 +49,7 @@ declaration: Function - root
)"},
{R"cpp(
namespace root {
-struct S { static const int x = 0; };
+struct S { static const int x = 0; ~S(); };
int y = S::x + root::S().x;
}
)cpp",
@@ -60,10 +60,12 @@ declaration: Namespace - root
type: Qualified - const
type: Builtin - int
expression: IntegerLiteral - 0
+ declaration: CXXDestructor
+ type: Record - S
+ type: FunctionProto
+ type: Builtin - void
declaration: CXXConstructor
declaration: CXXConstructor
- declaration: CXXConstructor
- declaration: CXXDestructor
declaration: Var - y
type: Builtin - int
expression: ExprWithCleanups
@@ -74,7 +76,7 @@ declaration: Namespace - root
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
- expression: MaterializeTemporary - rvalue
+ expression: CXXBindTemporary
expression: CXXTemporaryObject - S
type: Elaborated
specifier: Namespace - root::
@@ -82,6 +84,37 @@ declaration: Namespace - root
)"},
{R"cpp(
namespace root {
+struct S { static const int x = 0; };
+int y = S::x + root::S().x;
+}
+ )cpp",
+ R"(
+declaration: Namespace - root
+ declaration: CXXRecord - S
+ declaration: Var - x
+ type: Qualified - const
+ type: Builtin - int
+ expression: IntegerLiteral - 0
+ declaration: CXXConstructor
+ declaration: CXXConstructor
+ declaration: CXXConstructor
+ declaration: CXXDestructor
+ declaration: Var - y
+ type: Builtin - int
+ expression: BinaryOperator - +
+ expression: ImplicitCast - LValueToRValue
+ expression: DeclRef - x
+ specifier: TypeSpec
+ type: Record - S
+ expression: ImplicitCast - LValueToRValue
+ expression: Member - x
+ expression: CXXTemporaryObject - S
+ type: Elaborated
+ specifier: Namespace - root::
+ type: Record - S
+ )"},
+ {R"cpp(
+namespace root {
template <typename T> int tmpl() {
(void)tmpl<unsigned>();
return T::value;
>From a353728d94fb390057facf3a248e895c19e83956 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Wed, 12 Jun 2024 18:21:15 +0100
Subject: [PATCH 3/6] Run [[nodiscard]] test in C++23
---
clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
index a088c9ab93b0e..fb767641cdf33 100644
--- a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
@@ -1,6 +1,7 @@
-// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected -Wc++20-extensions %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17 -Wc++17-extensions %s
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify=expected,cxx11-17,cxx11 -Wc++17-extensions -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17 -Wc++17-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify=expected %s
struct [[nodiscard]] S {};
S get_s();
>From cc2c63747cfcafdc83fb53c7987857ce2464fa9f Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Fri, 14 Jun 2024 15:21:51 +0100
Subject: [PATCH 4/6] Change __declspec(property) handling; Refactor code;
Tests
Create Sema::DiagnoseDiscardedNodiscard
Remove warn_discarded_class_member_access and call Sema::DiagnoseDiscardedNodiscard instead
Remove MakeGLValue from the MSPropertyRefExpr path
Fix [[nodiscard]] test in test/CXX
Add test for [[nodiscard]] and explicit object member functions in test/SemaCXX
---
.../clang/Basic/DiagnosticSemaKinds.td | 3 -
clang/include/clang/Sema/Sema.h | 5 +
clang/lib/AST/Expr.cpp | 3 +
clang/lib/Sema/SemaExprMember.cpp | 16 +--
clang/lib/Sema/SemaStmt.cpp | 114 ++++++++++--------
.../dcl.attr/dcl.attr.nodiscard/p2.cpp | 40 +++---
clang/test/SemaCXX/ms-property.cpp | 42 ++++++-
7 files changed, 137 insertions(+), 86 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 008bf5fa0ccfc..193eae3bc41d6 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -9182,9 +9182,6 @@ def warn_unused_constructor : Warning<
def warn_unused_constructor_msg : Warning<
"ignoring temporary created by a constructor declared with %0 attribute: %1">,
InGroup<UnusedValue>;
-def warn_discarded_class_member_access : Warning<
- "left operand of dot in this class member access is discarded and has no effect">,
- InGroup<UnusedValue>;
def warn_side_effects_unevaluated_context : Warning<
"expression with side effects has no effect in an unevaluated context">,
InGroup<UnevaluatedExpression>;
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index 4d4579fcfd456..bc17cced10b10 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -8502,6 +8502,11 @@ class Sema final : public SemaBase {
SourceLocation EndLoc);
void ActOnForEachDeclStmt(DeclGroupPtrTy Decl);
+ /// DiagnoseDiscardedNodiscard - Given an expression that is semantically
+ /// a discarded-value expression, diagnose if any [[nodiscard]] value
+ /// has been discarded
+ void DiagnoseDiscardedNodiscard(const Expr *E);
+
/// DiagnoseUnusedExprResult - If the statement passed in is an expression
/// whose result is unused, warn.
void DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID);
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index f9d634550dc06..db2796679859b 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2962,6 +2962,9 @@ bool Expr::isUnusedResultAWarning(const Expr *&WarnE, SourceLocation &Loc,
case ExprWithCleanupsClass:
return cast<ExprWithCleanups>(this)->getSubExpr()
->isUnusedResultAWarning(WarnE, Loc, R1, R2, Ctx);
+ case OpaqueValueExprClass:
+ return cast<OpaqueValueExpr>(this)->getSourceExpr()->isUnusedResultAWarning(
+ WarnE, Loc, R1, R2, Ctx);
}
}
diff --git a/clang/lib/Sema/SemaExprMember.cpp b/clang/lib/Sema/SemaExprMember.cpp
index 4679fe529ac91..ad186c3012344 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1136,7 +1136,7 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
// an enumerator, the first expression is a discarded-value expression; if
// the id-expression names a non-static data member, the first expression
// shall be a glvalue.
- auto MakeDiscardedValue = [&BaseExpr, IsArrow, this] {
+ auto MakeDiscardedValue = [&] {
assert(getLangOpts().CPlusPlus &&
"Static member / member enumerator outside of C++");
if (IsArrow)
@@ -1145,11 +1145,10 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
if (Converted.isInvalid())
return true;
BaseExpr = Converted.get();
- DiagnoseUnusedExprResult(BaseExpr,
- diag::warn_discarded_class_member_access);
+ DiagnoseDiscardedNodiscard(BaseExpr);
return false;
};
- auto MakeGLValue = [&BaseExpr, IsArrow, this] {
+ auto MakeGLValue = [&] {
if (IsArrow || !BaseExpr->isPRValue())
return false;
ExprResult Converted = TemporaryMaterializationConversion(BaseExpr);
@@ -1171,10 +1170,11 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType BaseExprType,
}
if (MSPropertyDecl *PD = dyn_cast<MSPropertyDecl>(MemberDecl)) {
- // Properties treated as non-static data members for the purpose of
- // temporary materialization
- if (MakeGLValue())
- return ExprError();
+ // No temporaries are materialized for property references yet.
+ // They might be materialized when this is transformed into a member call.
+ // Note that this is slightly different behaviour from MSVC which doesn't
+ // implement CWG2813 yet: MSVC might materialize an extra temporary if the
+ // getter or setter function is an explicit object member function.
return BuildMSPropertyRefExpr(*this, BaseExpr, IsArrow, SS, PD,
MemberNameInfo);
}
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 7effaa943a226..26ea3bebd9968 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -222,18 +222,13 @@ static bool DiagnoseNoDiscard(Sema &S, const WarnUnusedResultAttr *A,
return S.Diag(Loc, diag::warn_unused_result_msg) << A << Msg << R1 << R2;
}
-void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
- const unsigned OrigDiagID = DiagID;
- if (const LabelStmt *Label = dyn_cast_or_null<LabelStmt>(S))
- return DiagnoseUnusedExprResult(Label->getSubStmt(), DiagID);
-
- const Expr *E = dyn_cast_or_null<Expr>(S);
- if (!E)
- return;
+namespace {
+void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
+ bool NoDiscardOnly = !DiagID.has_value();
// If we are in an unevaluated expression context, then there can be no unused
// results because the results aren't expected to be used in the first place.
- if (isUnevaluatedContext())
+ if (S.isUnevaluatedContext())
return;
SourceLocation ExprLoc = E->IgnoreParenImpCasts()->getExprLoc();
@@ -242,30 +237,31 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
// expression is a call to a function with the warn_unused_result attribute,
// we warn no matter the location. Because of the order in which the various
// checks need to happen, we factor out the macro-related test here.
- bool ShouldSuppress =
- SourceMgr.isMacroBodyExpansion(ExprLoc) ||
- SourceMgr.isInSystemMacro(ExprLoc);
+ bool ShouldSuppress = S.SourceMgr.isMacroBodyExpansion(ExprLoc) ||
+ S.SourceMgr.isInSystemMacro(ExprLoc);
const Expr *WarnExpr;
SourceLocation Loc;
SourceRange R1, R2;
- if (!E->isUnusedResultAWarning(WarnExpr, Loc, R1, R2, Context))
+ if (!E->isUnusedResultAWarning(WarnExpr, Loc, R1, R2, S.Context))
return;
- // If this is a GNU statement expression expanded from a macro, it is probably
- // unused because it is a function-like macro that can be used as either an
- // expression or statement. Don't warn, because it is almost certainly a
- // false positive.
- if (isa<StmtExpr>(E) && Loc.isMacroID())
- return;
-
- // Check if this is the UNREFERENCED_PARAMETER from the Microsoft headers.
- // That macro is frequently used to suppress "unused parameter" warnings,
- // but its implementation makes clang's -Wunused-value fire. Prevent this.
- if (isa<ParenExpr>(E->IgnoreImpCasts()) && Loc.isMacroID()) {
- SourceLocation SpellLoc = Loc;
- if (findMacroSpelling(SpellLoc, "UNREFERENCED_PARAMETER"))
+ if (!NoDiscardOnly) {
+ // If this is a GNU statement expression expanded from a macro, it is
+ // probably unused because it is a function-like macro that can be used as
+ // either an expression or statement. Don't warn, because it is almost
+ // certainly a false positive.
+ if (isa<StmtExpr>(E) && Loc.isMacroID())
return;
+
+ // Check if this is the UNREFERENCED_PARAMETER from the Microsoft headers.
+ // That macro is frequently used to suppress "unused parameter" warnings,
+ // but its implementation makes clang's -Wunused-value fire. Prevent this.
+ if (isa<ParenExpr>(E->IgnoreImpCasts()) && Loc.isMacroID()) {
+ SourceLocation SpellLoc = Loc;
+ if (S.findMacroSpelling(SpellLoc, "UNREFERENCED_PARAMETER"))
+ return;
+ }
}
// Okay, we have an unused result. Depending on what the base expression is,
@@ -276,7 +272,7 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (const CXXBindTemporaryExpr *TempExpr = dyn_cast<CXXBindTemporaryExpr>(E))
E = TempExpr->getSubExpr();
- if (DiagnoseUnusedComparison(*this, E))
+ if (DiagnoseUnusedComparison(S, E))
return;
E = WarnExpr;
@@ -289,8 +285,9 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (E->getType()->isVoidType())
return;
- if (DiagnoseNoDiscard(*this, cast_or_null<WarnUnusedResultAttr>(
- CE->getUnusedResultAttr(Context)),
+ if (DiagnoseNoDiscard(S,
+ cast_if_present<WarnUnusedResultAttr>(
+ CE->getUnusedResultAttr(S.Context)),
Loc, R1, R2, /*isCtor=*/false))
return;
@@ -302,11 +299,11 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (ShouldSuppress)
return;
if (FD->hasAttr<PureAttr>()) {
- Diag(Loc, diag::warn_unused_call) << R1 << R2 << "pure";
+ S.Diag(Loc, diag::warn_unused_call) << R1 << R2 << "pure";
return;
}
if (FD->hasAttr<ConstAttr>()) {
- Diag(Loc, diag::warn_unused_call) << R1 << R2 << "const";
+ S.Diag(Loc, diag::warn_unused_call) << R1 << R2 << "const";
return;
}
}
@@ -314,14 +311,14 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (const CXXConstructorDecl *Ctor = CE->getConstructor()) {
const auto *A = Ctor->getAttr<WarnUnusedResultAttr>();
A = A ? A : Ctor->getParent()->getAttr<WarnUnusedResultAttr>();
- if (DiagnoseNoDiscard(*this, A, Loc, R1, R2, /*isCtor=*/true))
+ if (DiagnoseNoDiscard(S, A, Loc, R1, R2, /*isCtor=*/true))
return;
}
} else if (const auto *ILE = dyn_cast<InitListExpr>(E)) {
if (const TagDecl *TD = ILE->getType()->getAsTagDecl()) {
- if (DiagnoseNoDiscard(*this, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
- R2, /*isCtor=*/false))
+ if (DiagnoseNoDiscard(S, TD->getAttr<WarnUnusedResultAttr>(), Loc, R1, R2,
+ /*isCtor=*/false))
return;
}
} else if (ShouldSuppress)
@@ -329,23 +326,23 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
E = WarnExpr;
if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(E)) {
- if (getLangOpts().ObjCAutoRefCount && ME->isDelegateInitCall()) {
- Diag(Loc, diag::err_arc_unused_init_message) << R1;
+ if (S.getLangOpts().ObjCAutoRefCount && ME->isDelegateInitCall()) {
+ S.Diag(Loc, diag::err_arc_unused_init_message) << R1;
return;
}
const ObjCMethodDecl *MD = ME->getMethodDecl();
if (MD) {
- if (DiagnoseNoDiscard(*this, MD->getAttr<WarnUnusedResultAttr>(), Loc, R1,
- R2, /*isCtor=*/false))
+ if (DiagnoseNoDiscard(S, MD->getAttr<WarnUnusedResultAttr>(), Loc, R1, R2,
+ /*isCtor=*/false))
return;
}
} else if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) {
const Expr *Source = POE->getSyntacticForm();
// Handle the actually selected call of an OpenMP specialized call.
- if (LangOpts.OpenMP && isa<CallExpr>(Source) &&
+ if (S.LangOpts.OpenMP && isa<CallExpr>(Source) &&
POE->getNumSemanticExprs() == 1 &&
isa<CallExpr>(POE->getSemanticExpr(0)))
- return DiagnoseUnusedExprResult(POE->getSemanticExpr(0), DiagID);
+ return DiagnoseUnused(S, POE->getSemanticExpr(0), DiagID);
if (isa<ObjCSubscriptRefExpr>(Source))
DiagID = diag::warn_unused_container_subscript_expr;
else if (isa<ObjCPropertyRefExpr>(Source))
@@ -362,17 +359,21 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
if (!RD->getAttr<WarnUnusedAttr>())
return;
}
+
+ if (NoDiscardOnly)
+ return;
+
// Diagnose "(void*) blah" as a typo for "(void) blah".
- else if (const CStyleCastExpr *CE = dyn_cast<CStyleCastExpr>(E)) {
+ if (const CStyleCastExpr *CE = dyn_cast<CStyleCastExpr>(E)) {
TypeSourceInfo *TI = CE->getTypeInfoAsWritten();
QualType T = TI->getType();
// We really do want to use the non-canonical type here.
- if (T == Context.VoidPtrTy) {
+ if (T == S.Context.VoidPtrTy) {
PointerTypeLoc TL = TI->getTypeLoc().castAs<PointerTypeLoc>();
- Diag(Loc, diag::warn_unused_voidptr)
- << FixItHint::CreateRemoval(TL.getStarLoc());
+ S.Diag(Loc, diag::warn_unused_voidptr)
+ << FixItHint::CreateRemoval(TL.getStarLoc());
return;
}
}
@@ -381,23 +382,34 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
// isn't an array.
if (E->isGLValue() && E->getType().isVolatileQualified() &&
!E->getType()->isArrayType()) {
- Diag(Loc, diag::warn_unused_volatile) << R1 << R2;
+ S.Diag(Loc, diag::warn_unused_volatile) << R1 << R2;
return;
}
// Do not diagnose use of a comma operator in a SFINAE context because the
// type of the left operand could be used for SFINAE, so technically it is
// *used*.
- if (DiagID == diag::warn_unused_comma_left_operand && isSFINAEContext())
+ if (DiagID == diag::warn_unused_comma_left_operand && S.isSFINAEContext())
return;
- // Don't diagnose discarded left of dot in static class member access
- // because its type is "used" to determine the class to access
- if (OrigDiagID == diag::warn_discarded_class_member_access)
+ S.DiagIfReachable(Loc, llvm::ArrayRef<const Stmt *>(E),
+ S.PDiag(*DiagID) << R1 << R2);
+}
+} // namespace
+
+void Sema::DiagnoseDiscardedNodiscard(const Expr *E) {
+ DiagnoseUnused(*this, E, std::nullopt);
+}
+
+void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
+ if (const LabelStmt *Label = dyn_cast_if_present<LabelStmt>(S))
+ S = Label->getSubStmt();
+
+ const Expr *E = dyn_cast_if_present<Expr>(S);
+ if (!E)
return;
- DiagIfReachable(Loc, S ? llvm::ArrayRef(S) : std::nullopt,
- PDiag(DiagID) << R1 << R2);
+ DiagnoseUnused(*this, E, DiagID);
}
void Sema::ActOnStartOfCompoundStmt(bool IsStmtExpr) {
diff --git a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
index fb767641cdf33..8e83fd6f07fab 100644
--- a/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
+++ b/clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
@@ -1,17 +1,21 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify=expected,cxx11-17,cxx11 -Wc++17-extensions -Wc++20-extensions %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17 -Wc++17-extensions %s
-// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify=expected,cxx11-17 -Wc++20-extensions %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected %s
// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify=expected %s
struct [[nodiscard]] S {};
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
S get_s();
S& get_s_ref();
enum [[nodiscard]] E {};
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
E get_e();
[[nodiscard]] int get_i();
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard]] volatile int &get_vi();
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
void f() {
get_s(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
@@ -28,6 +32,7 @@ void f() {
}
[[nodiscard]] volatile char &(*fp)(); // expected-warning {{'nodiscard' attribute only applies to functions, classes, or enumerations}}
+// cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
void g() {
fp(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
@@ -64,15 +69,20 @@ void f() {
} // namespace PR31526
struct [[nodiscard("reason")]] ReasonStruct {};
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
struct LaterReason;
struct [[nodiscard("later reason")]] LaterReason {};
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
ReasonStruct get_reason();
LaterReason get_later_reason();
[[nodiscard("another reason")]] int another_reason();
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
[[nodiscard("conflicting reason")]] int conflicting_reason();
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
[[nodiscard("special reason")]] int conflicting_reason();
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
void cxx20_use() {
get_reason(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: reason}}
@@ -83,17 +93,23 @@ void cxx20_use() {
namespace p1771 {
struct[[nodiscard("Don't throw me away!")]] ConvertTo{};
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
struct S {
[[nodiscard]] S();
+ // cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard("Don't let that S-Char go!")]] S(char);
+ // cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
S(int);
[[gnu::warn_unused_result]] S(double);
operator ConvertTo();
[[nodiscard]] operator int();
+ // cxx11-warning at -1 {{use of the 'nodiscard' attribute is a C++17 extension}}
[[nodiscard("Don't throw away as a double")]] operator double();
+ // cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
};
struct[[nodiscard("Don't throw me away either!")]] Y{};
+// cxx11-17-warning at -1 {{use of the 'nodiscard' attribute is a C++20 extension}}
void usage() {
S(); // expected-warning {{ignoring temporary created by a constructor declared with 'nodiscard' attribute}}
@@ -123,7 +139,7 @@ void usage() {
static_cast<int>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
static_cast<double>(s); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute: Don't throw away as a double}}
}
-}; // namespace p1771
+} // namespace p1771
namespace discarded_member_access {
struct X {
@@ -171,21 +187,3 @@ void f() {
#endif
}
} // namespace discarded_member_access
-
-
-// cxx11-warning at 5 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 9 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 12 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 13 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 29 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 65 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 67 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 71 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 73 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 74 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 84 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 86 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 87 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 91 {{use of the 'nodiscard' attribute is a C++17 extension}}
-// cxx11-warning at 92 {{use of the 'nodiscard' attribute is a C++20 extension}}
-// cxx11-warning at 95 {{use of the 'nodiscard' attribute is a C++20 extension}}
diff --git a/clang/test/SemaCXX/ms-property.cpp b/clang/test/SemaCXX/ms-property.cpp
index 168987b246223..d5799a8a4d363 100644
--- a/clang/test/SemaCXX/ms-property.cpp
+++ b/clang/test/SemaCXX/ms-property.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -ast-print -verify -triple=x86_64-pc-win32 -fms-compatibility %s -o - | FileCheck %s
-// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fms-compatibility -emit-pch -o %t %s
-// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fms-compatibility -include-pch %t -verify %s -ast-print -o - | FileCheck %s
-// expected-no-diagnostics
+// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fms-compatibility -emit-pch -o %t -verify %s
+// RUN: %clang_cc1 -triple=x86_64-pc-win32 -fms-compatibility -include-pch %t %s -ast-print -o - | FileCheck %s
+// RUN: %clang_cc1 -fdeclspec -fsyntax-only -verify %s -std=c++23
#ifndef HEADER
#define HEADER
@@ -85,4 +85,40 @@ int main(int argc, char **argv) {
// CHECK-NEXT: return Test1::GetTest1()->X;
return Test1::GetTest1()->X;
}
+
+struct X {
+ int implicit_object_member_function() { return 0; }
+ static int static_member_function() { return 0; }
+
+ __declspec(property(get=implicit_object_member_function)) int imp;
+ __declspec(property(get=static_member_function)) int st;
+
+#if __cplusplus >= 202302L
+ int explicit_object_member_function(this X self) { return 0; }
+ __declspec(property(get=explicit_object_member_function)) int exp;
+#endif
+};
+
+[[nodiscard]] X get_x();
+void f() {
+ (void) get_x().imp;
+ (void) get_x().st;
+ // expected-warning at -1 {{ignoring return value of function declared with 'nodiscard' attribute}}
+#if __cplusplus >= 202302L
+ (void) get_x().exp;
+#endif
+}
+
+#if __cplusplus >= 202302L
+struct Y {
+ Y() = default;
+ Y(const Y&) = delete;
+ int explicit_object_member_function(this Y) { return 0; }
+ __declspec(property(get = explicit_object_member_function)) int prop;
+};
+void g() {
+ (void) Y().prop;
+}
+#endif
+
#endif // HEADER
>From 7f08f2b5fc5c387feb81672293b6fa3c4ace34ad Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Fri, 14 Jun 2024 17:09:14 +0100
Subject: [PATCH 5/6] [WIP] do not commit
---
.../include/clang/Basic/DiagnosticASTKinds.td | 2 -
.../clang/Basic/DiagnosticSemaKinds.td | 3 ++
clang/lib/AST/ExprConstant.cpp | 38 +++++++------------
clang/lib/CodeGen/CGExprAgg.cpp | 36 +++++-------------
clang/lib/Sema/SemaInit.cpp | 37 ++++++++++++++++++
5 files changed, 63 insertions(+), 53 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index a024f9b2a9f8c..f004766451de3 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -393,8 +393,6 @@ def note_constexpr_delete_base_nonvirt_dtor : Note<
def note_constexpr_memory_leak : Note<
"allocation performed here was not deallocated"
"%plural{0:|: (along with %0 other memory leak%s0)}0">;
-def note_constexpr_unsupported_layout : Note<
- "type %0 has unexpected layout">;
def note_constexpr_unsupported_flexible_array : Note<
"flexible array initialization is not yet supported">;
def note_constexpr_non_const_vectorelements : Note<
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index 193eae3bc41d6..31e7ada81b71b 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -12213,6 +12213,9 @@ def err_std_source_location_impl_not_found : Error<
def err_std_source_location_impl_malformed : Error<
"'std::source_location::__impl' must be standard-layout and have only two 'const char *' fields '_M_file_name' and '_M_function_name', and two integral fields '_M_line' and '_M_column'">;
+def err_std_initializer_list_malformed : Error<
+ "'std::initializer_list<%0>' not recognized. Must be a struct with two fields, a 'const E *' and either another 'const E *' or a 'std::size_t''">;
+
// HLSL Diagnostics
def err_hlsl_attr_unsupported_in_stage : Error<"attribute %0 is unsupported in '%1' shaders, requires %select{|one of the following: }2%3">;
def err_hlsl_attr_invalid_type : Error<
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 86fb396fabe2d..240cf46d6a6b3 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -10534,42 +10534,30 @@ bool RecordExprEvaluator::VisitCXXStdInitializerListExpr(
return false;
};
- // FIXME: Perform the checks on the field types in SemaInit.
- RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
- RecordDecl::field_iterator Field = Record->field_begin();
- if (Field == Record->field_end())
- return InvalidType();
-
- // Start pointer.
- if (!Field->getType()->isPointerType() ||
- !Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
- ArrayType->getElementType()))
- return InvalidType();
-
- // FIXME: What if the initializer_list type has base classes, etc?
+ // FIXME: Call default constructor? Default constructors of base classes if any?
Result = APValue(APValue::UninitStruct(), 0, 2);
Array.moveInto(Result.getStructField(0));
- if (++Field == Record->field_end())
- return InvalidType();
+ RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
+ RecordDecl::field_iterator Field = Record->field_begin();
+ assert(Field != Record->field_end() && Info.Ctx.hasSameType(Field->getType()->getPointeeType(), ArrayType->getElementType()) && "Expected std::initializer_list first field to be const E *");
+ ++Field;
+ assert(Field != Record->field_end() && "Expected std::initializer_list to have two fields");
- if (Field->getType()->isPointerType() &&
- Info.Ctx.hasSameType(Field->getType()->getPointeeType(),
- ArrayType->getElementType())) {
+ if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType())) {
+ // Length.
+ Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
+ } else {
// End pointer.
+ assert(Info.Ctx.hasSameType(Field->getType()->getPointeeType(), ArrayType->getElementType()) && "Expected std::initializer_list second field to be const E *");
if (!HandleLValueArrayAdjustment(Info, E, Array,
ArrayType->getElementType(),
ArrayType->getZExtSize()))
return false;
Array.moveInto(Result.getStructField(1));
- } else if (Info.Ctx.hasSameType(Field->getType(), Info.Ctx.getSizeType()))
- // Length.
- Result.getStructField(1) = APValue(APSInt(ArrayType->getSize()));
- else
- return InvalidType();
+ }
- if (++Field != Record->field_end())
- return InvalidType();
+ assert(++Field == Record->field_end() && "Expected std::initializer_list to only have two fields");
return true;
}
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index b2a5ceeeae08b..1fbab5121d077 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -429,53 +429,37 @@ AggExprEmitter::VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *E) {
Ctx.getAsConstantArrayType(E->getSubExpr()->getType());
assert(ArrayType && "std::initializer_list constructed from non-array");
- // FIXME: Perform the checks on the field types in SemaInit.
RecordDecl *Record = E->getType()->castAs<RecordType>()->getDecl();
RecordDecl::field_iterator Field = Record->field_begin();
- if (Field == Record->field_end()) {
- CGF.ErrorUnsupported(E, "weird std::initializer_list");
- return;
- }
+ assert(Field != Record->field_end() && Ctx.hasSameType(Field->getType()->getPointeeType(), ArrayType->getElementType()) && "Expected std::initializer_list first field to be const E *");
// Start pointer.
- if (!Field->getType()->isPointerType() ||
- !Ctx.hasSameType(Field->getType()->getPointeeType(),
- ArrayType->getElementType())) {
- CGF.ErrorUnsupported(E, "weird std::initializer_list");
- return;
- }
-
AggValueSlot Dest = EnsureSlot(E->getType());
LValue DestLV = CGF.MakeAddrLValue(Dest.getAddress(), E->getType());
LValue Start = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
llvm::Value *ArrayStart = ArrayPtr.emitRawPointer(CGF);
CGF.EmitStoreThroughLValue(RValue::get(ArrayStart), Start);
++Field;
-
- if (Field == Record->field_end()) {
- CGF.ErrorUnsupported(E, "weird std::initializer_list");
- return;
- }
+ assert(Field != Record->field_end() && "Expected std::initializer_list to have two fields");
llvm::Value *Size = Builder.getInt(ArrayType->getSize());
LValue EndOrLength = CGF.EmitLValueForFieldInitialization(DestLV, *Field);
- if (Field->getType()->isPointerType() &&
- Ctx.hasSameType(Field->getType()->getPointeeType(),
- ArrayType->getElementType())) {
+ if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
+ // Length.
+ CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
+
+ } else {
// End pointer.
+ assert(Field->getType()->isPointerType() && Ctx.hasSameType(Field->getType()->getPointeeType(), ArrayType->getElementType()) && "Expected std::initializer_list second field to be const E *");
llvm::Value *Zero = llvm::ConstantInt::get(CGF.PtrDiffTy, 0);
llvm::Value *IdxEnd[] = { Zero, Size };
llvm::Value *ArrayEnd = Builder.CreateInBoundsGEP(
ArrayPtr.getElementType(), ArrayPtr.emitRawPointer(CGF), IdxEnd,
"arrayend");
CGF.EmitStoreThroughLValue(RValue::get(ArrayEnd), EndOrLength);
- } else if (Ctx.hasSameType(Field->getType(), Ctx.getSizeType())) {
- // Length.
- CGF.EmitStoreThroughLValue(RValue::get(Size), EndOrLength);
- } else {
- CGF.ErrorUnsupported(E, "weird std::initializer_list");
- return;
}
+
+ assert(++Field == Record->field_end() && "Expected std::initializer_list to only have two fields");
}
/// Determine if E is a trivial array filler, that is, one that is
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index ed8b226a6b39f..92778bf6f9961 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -9378,6 +9378,43 @@ ExprResult InitializationSequence::Perform(Sema &S,
// Wrap it in a construction of a std::initializer_list<T>.
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);
+ if (!Step->Type->isDependentType()) {
+ assert(S.isCompleteType(CurInit.get()->getExprLoc(), Step->Type, Sema::CompleteTypeKind::Normal) && "std::initializer_list<E> incomplete when used during initialization");
+ QualType ElementType;
+ [[maybe_unused]] bool IsStdInitializerList = S.isStdInitializerList(Step->Type, &ElementType);
+ assert(IsStdInitializerList && "StdInitializerList step to non-std::initializer_list");
+
+ auto InvalidType = [&] {
+ S.Diag(CurInit, diag::err_std_initializer_list_malformed)
+ << ElementType;
+ return ExprError();
+ };
+
+ RecordDecl *Record = Step->Type->castAs<RecordType>()->getDecl();
+ RecordDecl::field_iterator Field = Record->field_begin();
+ if (Field == Record->field_end())
+ return InvalidType();
+
+ // FIXME: What if the initializer_list type has base classes, etc?
+ // Start pointer.
+ if (!Field->getType()->isPointerType() || !S.Context.hasSameType(Field->getType()->getPointeeType(), ElementType.withConst()))
+ return InvalidType();
+
+ if (++Field == Record->field_end())
+ return InvalidType();
+
+ if (Field->getType()->isPointerType()) {
+ if (!S.Context.hasSameType(Field->getType()->getPointeeType(), ElementType.withConst()))
+ return InvalidType();
+ } else {
+ if (Field->isUnnamedBitField() || !S.Context.hasSameType(Field->getType(), S.Context.getSizeType()))
+ return InvalidType();
+ }
+
+ if (++Field != Record->field_end())
+ return InvalidType();
+ }
+
// Bind the result, in case the library has given initializer_list a
// non-trivial destructor.
if (shouldBindAsTemporary(Entity))
>From 37f24484fcb0a71cef93928ab2f8e6c689c34868 Mon Sep 17 00:00:00 2001
From: Mital Ashok <mital at mitalashok.co.uk>
Date: Mon, 17 Jun 2024 20:42:33 +0100
Subject: [PATCH 6/6] anonymous namespace -> static
---
clang/lib/Sema/SemaStmt.cpp | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Sema/SemaStmt.cpp b/clang/lib/Sema/SemaStmt.cpp
index 4189b5126f8ac..d993f61f240c3 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -222,8 +222,12 @@ static bool DiagnoseNoDiscard(Sema &S, const WarnUnusedResultAttr *A,
return S.Diag(Loc, diag::warn_unused_result_msg) << A << Msg << R1 << R2;
}
-namespace {
-void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
+static void DiagnoseUnused(Sema &S, const Expr *E,
+ std::optional<unsigned> DiagID) {
+ // When called from Sema::DiagnoseUnusedExprResult, DiagID is a diagnostic for
+ // where this expression is not used. When called from
+ // Sema::DiagnoseDiscardedNodiscard, DiagID is std::nullopt and this function
+ // will only diagnose [[nodiscard]], [[gnu::warn_unused_result]] and similar
bool NoDiscardOnly = !DiagID.has_value();
// If we are in an unevaluated expression context, then there can be no unused
@@ -395,7 +399,6 @@ void DiagnoseUnused(Sema &S, const Expr *E, std::optional<unsigned> DiagID) {
S.DiagIfReachable(Loc, llvm::ArrayRef<const Stmt *>(E),
S.PDiag(*DiagID) << R1 << R2);
}
-} // namespace
void Sema::DiagnoseDiscardedNodiscard(const Expr *E) {
DiagnoseUnused(*this, E, std::nullopt);
More information about the cfe-commits
mailing list