Author: cor3ntin
Date: 2024-12-18T10:44:42+01:00
New Revision: db93ef14aef9c572e02bc842762bc4d0278148f9

URL: 
https://github.com/llvm/llvm-project/commit/db93ef14aef9c572e02bc842762bc4d0278148f9
DIFF: 
https://github.com/llvm/llvm-project/commit/db93ef14aef9c572e02bc842762bc4d0278148f9.diff

LOG: [Clang] Implement CWG2813: Class member access with prvalues (#120223)

This is a rebase of #95112 with my own feedback apply as @MitalAshok has
been inactive for a while.
It's fairly important this makes clang 20 as it is a blocker for #107451

--- 

[CWG2813](https://cplusplus.github.io/CWG/issues/2813.html)

prvalue.member_fn(expression-list) now will not materialize a temporary
for prvalue if member_fn is an explicit object member function, and
prvalue will bind directly to the object parameter.

The E1 in E1.static_member is now a discarded-value expression, so if E1
was a call to a [[nodiscard]] function, there will now be a warning.
This also affects C++98 with [[gnu::warn_unused_result]] functions.

This should not affect C where TemporaryMaterializationConversion is a
no-op.

Closes #100314
Fixes #100341

---------

Co-authored-by: Mital Ashok <mi...@mitalashok.co.uk>

Added: 
    

Modified: 
    clang-tools-extra/clangd/unittests/DumpASTTests.cpp
    clang/docs/ReleaseNotes.rst
    clang/include/clang/Sema/Sema.h
    clang/lib/AST/Expr.cpp
    clang/lib/Sema/SemaExprMember.cpp
    clang/lib/Sema/SemaOverload.cpp
    clang/lib/Sema/SemaStmt.cpp
    clang/test/CXX/dcl.dcl/dcl.attr/dcl.attr.nodiscard/p2.cpp
    clang/test/CXX/drs/cwg28xx.cpp
    clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
    clang/test/SemaCXX/cxx2b-deducing-this.cpp
    clang/test/SemaCXX/ms-property.cpp
    clang/www/cxx_dr_status.html

Removed: 
    


################################################################################
diff  --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp 
b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
index 304682118c871d..cb2c17ad4ef0d9 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;

diff  --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 408b2800f9e79c..956b5532b48f65 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -321,6 +321,11 @@ Resolutions to C++ Defect Reports
 - Fix name lookup for a dependent base class that is the current instantiation.
   (`CWG591: When a dependent base class is the current instantiation 
<https://cplusplus.github.io/CWG/issues/591.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/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index ae07ed8478f2a8..5ee7ea48cc983c 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -10659,6 +10659,11 @@ class Sema final : public SemaBase {
                            SourceLocation EndLoc);
   void ActOnForEachDeclStmt(DeclGroupPtrTy Decl);
 
+  /// DiagnoseDiscardedExprMarkedNodiscard - Given an expression that is
+  /// semantically a discarded-value expression, diagnose if any [[nodiscard]]
+  /// value has been discarded.
+  void DiagnoseDiscardedExprMarkedNodiscard(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 5a6738196d2890..8c8ccdb61dc01c 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -2990,6 +2990,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 85d5dfcb3db6de..bcc1b92ffdec73 100644
--- a/clang/lib/Sema/SemaExprMember.cpp
+++ b/clang/lib/Sema/SemaExprMember.cpp
@@ -1003,15 +1003,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();
@@ -1128,26 +1119,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 ConvertBaseExprToDiscardedValue = [&] {
+    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();
+    DiagnoseDiscardedExprMarkedNodiscard(BaseExpr);
+    return false;
+  };
+  auto ConvertBaseExprToGLValue = [&] {
+    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 (ConvertBaseExprToGLValue())
+      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)) {
+    // 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 
diff erent 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);
+  }
 
-  if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl))
+  if (IndirectFieldDecl *FD = dyn_cast<IndirectFieldDecl>(MemberDecl)) {
+    if (ConvertBaseExprToGLValue())
+      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 (ConvertBaseExprToDiscardedValue())
+      return ExprError();
     return BuildMemberExpr(BaseExpr, IsArrow, OpLoc,
                            SS.getWithLocInContext(Context), TemplateKWLoc, Var,
                            FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1161,7 +1194,13 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType 
BaseExprType,
     if (MemberFn->isInstance()) {
       valueKind = VK_PRValue;
       type = Context.BoundMemberTy;
+      if (MemberFn->isImplicitObjectMemberFunction() &&
+          ConvertBaseExprToGLValue())
+        return ExprError();
     } else {
+      // Static member function
+      if (ConvertBaseExprToDiscardedValue())
+        return ExprError();
       valueKind = VK_LValue;
       type = MemberFn->getType();
     }
@@ -1174,6 +1213,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType 
BaseExprType,
   assert(!isa<FunctionDecl>(MemberDecl) && "member function not C++ method?");
 
   if (EnumConstantDecl *Enum = dyn_cast<EnumConstantDecl>(MemberDecl)) {
+    if (ConvertBaseExprToDiscardedValue())
+      return ExprError();
     return BuildMemberExpr(
         BaseExpr, IsArrow, OpLoc, SS.getWithLocInContext(Context),
         TemplateKWLoc, Enum, FoundDecl, /*HadMultipleCandidates=*/false,
@@ -1181,6 +1222,8 @@ Sema::BuildMemberReferenceExpr(Expr *BaseExpr, QualType 
BaseExprType,
   }
 
   if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
+    if (ConvertBaseExprToDiscardedValue())
+      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 3dabe362802c90..fff49b759c935e 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -5933,7 +5933,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() !=
@@ -6464,11 +6466,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;
 }
 
@@ -15584,8 +15581,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 0e5c6cd49dccad..d9149f7ee40bbf 100644
--- a/clang/lib/Sema/SemaStmt.cpp
+++ b/clang/lib/Sema/SemaStmt.cpp
@@ -226,17 +226,18 @@ static bool DiagnoseNoDiscard(Sema &S, const NamedDecl 
*OffendingDecl,
   return S.Diag(Loc, diag::warn_unused_result) << A << true << Msg << R1 << R2;
 }
 
-void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned DiagID) {
-  if (const LabelStmt *Label = dyn_cast_or_null<LabelStmt>(S))
-    return DiagnoseUnusedExprResult(Label->getSubStmt(), DiagID);
+namespace {
 
-  const Expr *E = dyn_cast_or_null<Expr>(S);
-  if (!E)
-    return;
+// Diagnoses unused expressions that call functions marked [[nodiscard]],
+// [[gnu::warn_unused_result]] and similar.
+// Additionally, a DiagID can be provided to emit a warning in additional
+// contexts (such as for an unused LHS of a comma expression)
+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();
@@ -245,30 +246,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))
-    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())
+  if (!E->isUnusedResultAWarning(WarnExpr, Loc, R1, R2, S.Context))
     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,
@@ -279,7 +281,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;
@@ -293,8 +295,8 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, unsigned 
DiagID) {
     if (E->getType()->isVoidType())
       return;
 
-    auto [OffendingDecl, A] = CE->getUnusedResultAttr(Context);
-    if (DiagnoseNoDiscard(*this, OffendingDecl,
+    auto [OffendingDecl, A] = CE->getUnusedResultAttr(S.Context);
+    if (DiagnoseNoDiscard(S, OffendingDecl,
                           cast_or_null<WarnUnusedResultAttr>(A), Loc, R1, R2,
                           /*isCtor=*/false))
       return;
@@ -307,11 +309,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;
       }
     }
@@ -323,15 +325,15 @@ void Sema::DiagnoseUnusedExprResult(const Stmt *S, 
unsigned DiagID) {
         OffendingDecl = Ctor->getParent();
         A = OffendingDecl->getAttr<WarnUnusedResultAttr>();
       }
-      if (DiagnoseNoDiscard(*this, OffendingDecl, A, Loc, R1, R2,
+      if (DiagnoseNoDiscard(S, OffendingDecl, 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, TD->getAttr<WarnUnusedResultAttr>(), 
Loc,
-                            R1, R2, /*isCtor=*/false))
+      if (DiagnoseNoDiscard(S, TD, TD->getAttr<WarnUnusedResultAttr>(), Loc, 
R1,
+                            R2, /*isCtor=*/false))
         return;
     }
   } else if (ShouldSuppress)
@@ -339,23 +341,24 @@ 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, nullptr, 
MD->getAttr<WarnUnusedResultAttr>(),
-                            Loc, R1, R2, /*isCtor=*/false))
+      if (DiagnoseNoDiscard(S, nullptr, 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))
@@ -372,17 +375,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;
     }
   }
@@ -391,16 +398,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())
-    DiagIfReachable(Loc, S ? llvm::ArrayRef(S) : llvm::ArrayRef<Stmt *>(),
-                    PDiag(DiagID) << R1 << R2);
+  if (DiagID == diag::warn_unused_comma_left_operand && S.isSFINAEContext())
+    return;
+
+  S.DiagIfReachable(Loc, llvm::ArrayRef<const Stmt *>(E),
+                    S.PDiag(*DiagID) << R1 << R2);
+}
+} // namespace
+
+void Sema::DiagnoseDiscardedExprMarkedNodiscard(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;
+
+  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 da1f8201f55dcc..18f4bd5e9c0fae 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++11 -verify=expected,cxx11,cxx11-17 
-pedantic %s
 // RUN: %clang_cc1 -fsyntax-only -std=c++17 
-verify=expected,cxx11-17,since-cxx17 -pedantic %s
 // RUN: %clang_cc1 -fsyntax-only -std=c++20 -verify=expected,since-cxx17 
-pedantic %s
+// RUN: %clang_cc1 -fsyntax-only -std=c++23 -verify=expected,since-cxx17 
-pedantic %s
 
 struct [[nodiscard]] S {};
 // cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}}
@@ -134,3 +135,50 @@ void usage() {
   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 discarded_member_access {
+struct X {
+  union {
+    int variant_member;
+  };
+  struct { // expected-warning {{anonymous structs are a GNU extension}}
+    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@-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@-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@-1 {{ignoring return value of function declared with 
'nodiscard' attribute}}
+  (void) get_X().unscoped_enum;
+  // expected-warning@-1 {{ignoring return value of function declared with 
'nodiscard' attribute}}
+  (void) get_X().scoped_enum;
+  // expected-warning@-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@-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

diff  --git a/clang/test/CXX/drs/cwg28xx.cpp b/clang/test/CXX/drs/cwg28xx.cpp
index 9796607a790ce3..ff625a4a985bcc 100644
--- a/clang/test/CXX/drs/cwg28xx.cpp
+++ b/clang/test/CXX/drs/cwg28xx.cpp
@@ -30,7 +30,25 @@ using U2 = decltype(&main);
 #endif
 } // namespace cwg2811
 
-namespace cwg2819 { // cwg2819: 19
+namespace cwg2813 { // cwg2813: 20
+#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 cwg2813
+
+namespace cwg2819 { // cwg2819: 19 tentatively ready 2023-12-01
+
 #if __cpp_constexpr >= 202306L
   constexpr void* p = nullptr;
   constexpr int* q = static_cast<int*>(p);

diff  --git a/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp 
b/clang/test/CodeGenCXX/cxx2b-deducing-this.cpp
index 1c8835a3986ea0..8a78463d3a4955 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/test/SemaCXX/cxx2b-deducing-this.cpp 
b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
index 520052a89d1840..6f17ce72754560 100644
--- a/clang/test/SemaCXX/cxx2b-deducing-this.cpp
+++ b/clang/test/SemaCXX/cxx2b-deducing-this.cpp
@@ -437,6 +437,10 @@ namespace std {
   constexpr strong_ordering strong_ordering::equal = {0};
   constexpr strong_ordering strong_ordering::greater = {1};
   constexpr strong_ordering strong_ordering::less = {-1};
+
+  template<typename T> constexpr __remove_reference_t(T)&& move(T&& t) 
noexcept {
+    return static_cast<__remove_reference_t(T)&&>(t);
+  }
 }
 
 namespace operators_deduction {
@@ -965,6 +969,22 @@ void f();
 void a::f(this auto) {} // expected-error {{an explicit object parameter 
cannot appear in a non-member function}}
 }
 
+namespace GH100341 {
+struct X {
+    X() = default;
+    X(X&&) = default;
+    void operator()(this X);
+};
+
+void fail() {
+    X()();
+    [x = X{}](this auto) {}();
+}
+void pass() {
+    std::move(X())();
+    std::move([x = X{}](this auto) {})();
+}
+} // namespace GH100341
 struct R {
   void f(this auto &&self, int &&r_value_ref) {} // expected-note {{candidate 
function template not viable: expects an rvalue for 2nd argument}}
   void g(int &&r_value_ref) {

diff  --git a/clang/test/SemaCXX/ms-property.cpp 
b/clang/test/SemaCXX/ms-property.cpp
index 168987b2462233..d5799a8a4d3639 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@-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

diff  --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html
index cdedbcbaa40722..386c57250b7db6 100755
--- a/clang/www/cxx_dr_status.html
+++ b/clang/www/cxx_dr_status.html
@@ -16726,7 +16726,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>DRWP</td>
     <td>Class member access with prvalues</td>
-    <td class="unknown" align="center">Unknown</td>
+    <td class="unreleased" align="center">Clang 20</td>
   </tr>
   <tr id="2814">
     <td><a 
href="https://cplusplus.github.io/CWG/issues/2814.html";>2814</a></td>


        
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to