[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 <mital@mitalashok.co.uk>
This commit is contained in:
cor3ntin 2024-12-18 10:44:42 +01:00 committed by GitHub
parent 66bdbfbaa0
commit db93ef14ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 314 additions and 84 deletions

View File

@ -49,6 +49,41 @@ declaration: Function - root
)"},
{R"cpp(
namespace root {
struct S { static const int x = 0; ~S(); };
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: CXXDestructor
type: Record - S
type: FunctionProto
type: Builtin - void
declaration: CXXConstructor
declaration: CXXConstructor
declaration: Var - y
type: Builtin - int
expression: ExprWithCleanups
expression: BinaryOperator - +
expression: ImplicitCast - LValueToRValue
expression: DeclRef - x
specifier: TypeSpec
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: CXXBindTemporary
expression: CXXTemporaryObject - S
type: Elaborated
specifier: Namespace - root::
type: Record - S
)"},
{R"cpp(
namespace root {
struct S { static const int x = 0; };
int y = S::x + root::S().x;
}
@ -66,19 +101,17 @@ declaration: Namespace - root
declaration: CXXDestructor
declaration: Var - y
type: Builtin - int
expression: ExprWithCleanups
expression: BinaryOperator - +
expression: ImplicitCast - LValueToRValue
expression: DeclRef - x
specifier: TypeSpec
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
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: MaterializeTemporary - rvalue
expression: CXXTemporaryObject - S
type: Elaborated
specifier: Namespace - root::
type: Record - S
)"},
{R"cpp(
namespace root {

View File

@ -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
------------------

View File

@ -10659,6 +10659,11 @@ public:
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);

View File

@ -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);
}
}

View File

@ -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 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);
}
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);

View File

@ -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())

View File

@ -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))
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,
@ -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) {

View File

@ -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

View File

@ -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);

View File

@ -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: }

View File

@ -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) {

View File

@ -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

View File

@ -16726,7 +16726,7 @@ objects</td>
<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>