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 <mi...@mitalashok.co.uk> Date: Tue, 11 Jun 2024 14:26:38 +0100 Subject: [PATCH 1/4] [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@5 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@9 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@12 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@13 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@29 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@65 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@67 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@71 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@73 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@74 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@84 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@86 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@87 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@91 {{use of the 'nodiscard' attribute is a C++17 extension}} -// expected-warning@92 {{use of the 'nodiscard' attribute is a C++20 extension}} -// expected-warning@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@-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 + + +// cxx11-warning@5 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@9 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@12 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@13 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@29 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@65 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@67 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@71 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@73 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@74 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@84 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@86 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@87 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@91 {{use of the 'nodiscard' attribute is a C++17 extension}} +// cxx11-warning@92 {{use of the 'nodiscard' attribute is a C++20 extension}} +// cxx11-warning@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 <mi...@mitalashok.co.uk> Date: Tue, 11 Jun 2024 17:07:45 +0100 Subject: [PATCH 2/4] 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 <mi...@mitalashok.co.uk> Date: Wed, 12 Jun 2024 18:21:15 +0100 Subject: [PATCH 3/4] 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 <mi...@mitalashok.co.uk> Date: Fri, 14 Jun 2024 15:21:51 +0100 Subject: [PATCH 4/4] 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@-1 {{use of the 'nodiscard' attribute is a C++17 extension}} S get_s(); S& get_s_ref(); enum [[nodiscard]] E {}; +// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}} E get_e(); [[nodiscard]] int get_i(); +// cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}} [[nodiscard]] volatile int &get_vi(); +// cxx11-warning@-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@-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@-1 {{use of the 'nodiscard' attribute is a C++20 extension}} struct LaterReason; struct [[nodiscard("later reason")]] LaterReason {}; +// cxx11-17-warning@-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@-1 {{use of the 'nodiscard' attribute is a C++20 extension}} [[nodiscard("conflicting reason")]] int conflicting_reason(); +// cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}} [[nodiscard("special reason")]] int conflicting_reason(); +// cxx11-17-warning@-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@-1 {{use of the 'nodiscard' attribute is a C++20 extension}} struct S { [[nodiscard]] S(); + // cxx11-warning@-1 {{use of the 'nodiscard' attribute is a C++17 extension}} [[nodiscard("Don't let that S-Char go!")]] S(char); + // cxx11-17-warning@-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@-1 {{use of the 'nodiscard' attribute is a C++17 extension}} [[nodiscard("Don't throw away as a double")]] operator double(); + // cxx11-17-warning@-1 {{use of the 'nodiscard' attribute is a C++20 extension}} }; struct[[nodiscard("Don't throw me away either!")]] Y{}; +// cxx11-17-warning@-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@5 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@9 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@12 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@13 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@29 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@65 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@67 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@71 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@73 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@74 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@84 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@86 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@87 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@91 {{use of the 'nodiscard' attribute is a C++17 extension}} -// cxx11-warning@92 {{use of the 'nodiscard' attribute is a C++20 extension}} -// cxx11-warning@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@-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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits