Skip to content

Commit

Permalink
[Clang] Implement CWG2813: Class member access with prvalues (llvm#12…
Browse files Browse the repository at this point in the history
…0223)

This is a rebase of llvm#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 llvm#107451

Change-Id: I2261810f7c8d7dd8b3e3412c0814a528d7c7b91c

---

[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 llvm#100314
Fixes llvm#100341

---------

Co-authored-by: Mital Ashok <[email protected]>
(cherry picked from commit db93ef1)

Change-Id: Ic277d16bc8611b9d383cb890da3eda0ef1646555
  • Loading branch information
cor3ntin authored and ronlieb committed Dec 18, 2024
1 parent e8a79ad commit b21eb9e
Show file tree
Hide file tree
Showing 13 changed files with 303 additions and 74 deletions.
41 changes: 37 additions & 4 deletions clang-tools-extra/clangd/unittests/DumpASTTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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
Expand All @@ -74,14 +76,45 @@ 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::
type: Record - S
)"},
{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;
Expand Down
5 changes: 5 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,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
------------------

Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2988,6 +2988,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);
}
}

Expand Down
67 changes: 55 additions & 12 deletions clang/lib/Sema/SemaExprMember.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand All @@ -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();
}
Expand All @@ -1174,13 +1213,17 @@ 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,
MemberNameInfo, Enum->getType(), VK_PRValue, OK_Ordinary);
}

if (VarTemplateDecl *VarTempl = dyn_cast<VarTemplateDecl>(MemberDecl)) {
if (ConvertBaseExprToDiscardedValue())
return ExprError();
if (!TemplateArgs) {
diagnoseMissingTemplateArguments(
SS, /*TemplateKeyword=*/TemplateKWLoc.isValid(), VarTempl, MemberLoc);
Expand Down
11 changes: 3 additions & 8 deletions clang/lib/Sema/SemaOverload.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() !=
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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())
Expand Down
Loading

0 comments on commit b21eb9e

Please sign in to comment.