mirror of
https://github.com/llvm/llvm-project.git
synced 2025-04-14 17:56:39 +00:00
[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:
parent
66bdbfbaa0
commit
db93ef14ae
@ -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 {
|
||||
|
@ -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
|
||||
------------------
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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: }
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user