[llvm-branch-commits] [clang] [Clang] Implement CWG 2282 (PR #203832)
Igor Kudrin via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sun Jun 14 23:28:43 PDT 2026
https://github.com/igorkudrin created https://github.com/llvm/llvm-project/pull/203832
None
>From c73020466595bac07e1b59dc94c4be9ac392d41c Mon Sep 17 00:00:00 2001
From: Igor Kudrin <ikudrin at accesssoftek.com>
Date: Sat, 13 Jun 2026 23:14:25 -0700
Subject: [PATCH] [Clang] Implement CWG 2282
---
clang/lib/Sema/SemaExprCXX.cpp | 67 ++++++++++++-------
clang/test/CXX/drs/cwg5xx.cpp | 5 +-
.../test/CXX/expr/expr.unary/expr.new/p14.cpp | 13 ++--
clang/test/SemaCXX/new-delete.cpp | 13 ++--
.../std-align-val-t-in-operator-new.cpp | 6 ++
5 files changed, 65 insertions(+), 39 deletions(-)
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index fbbeb012c1728..7997ee4823369 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -2767,7 +2767,8 @@ static bool resolveAllocationOverload(
case OR_No_Viable_Function:
if (!PrefCandidates && !FallbackArgs.empty()) {
- PassAlignment = AlignedAllocationMode::No;
+ PassAlignment =
+ alignedAllocationModeFromBool(!isAlignedAllocation(PassAlignment));
return resolveAllocationOverload(S, R, Range, Mode, PrefArgs,
FallbackArgs, PassAlignment, Operator,
&Candidates, Diagnose);
@@ -2799,17 +2800,19 @@ static bool resolveAllocationOverload(
// If this is an allocation of the form 'new (p) X' for some object
// pointer p (or an expression that will decay to such a pointer),
// diagnose the reason for the error.
- if (!R.isClassLookup() && Args.size() == 2 &&
- (Args[1]->getType()->isObjectPointerType() ||
- Args[1]->getType()->isArrayType())) {
- const QualType Arg1Type = Args[1]->getType();
+ if (!R.isClassLookup() &&
+ (Args.size() == 2 ||
+ (Args.size() == 3 && isAlignedAllocation(PassAlignment))) &&
+ (Args.back()->getType()->isObjectPointerType() ||
+ Args.back()->getType()->isArrayType())) {
+ const QualType Arg1Type = Args.back()->getType();
QualType UnderlyingType = S.Context.getBaseElementType(Arg1Type);
if (UnderlyingType->isPointerType())
UnderlyingType = UnderlyingType->getPointeeType();
if (UnderlyingType.isConstQualified()) {
- S.Diag(Args[1]->getExprLoc(),
+ S.Diag(Args.back()->getExprLoc(),
diag::err_placement_new_into_const_qualified_storage)
- << Arg1Type << Args[1]->getSourceRange();
+ << Arg1Type << Args.back()->getSourceRange();
return true;
}
S.Diag(R.getNameLoc(), diag::err_need_header_before_placement_new)
@@ -2827,27 +2830,25 @@ static bool resolveAllocationOverload(
SmallVector<OverloadCandidate*, 32> PrefCands;
SmallVector<OverloadCandidate *, 32> Cands;
if (PrefCandidates) {
- assert(!isAlignedAllocation(PassAlignment) &&
- "This is a nested call that searches for an unaligned "
- "allocation function");
- assert(PrefArgs.size() == FallbackArgs.size() + 1 &&
- "FallbackArgs are PrefArgs with the alignment argument removed");
assert(Mode == ResolveMode::Untyped &&
"Typed mode does not print diagnostic");
- auto IsAligned = [](OverloadCandidate &C) {
+ bool PrefHasAlignArg = PrefArgs.size() > FallbackArgs.size();
+ auto IsPref = [PrefHasAlignArg](OverloadCandidate &C) {
const unsigned AlignArgOffset = 1;
- return C.Function->getNumParams() > AlignArgOffset &&
- C.Function->getParamDecl(AlignArgOffset)
- ->getType()
- ->isAlignValT();
+ bool CandidateHasAlignArg =
+ C.Function->getNumParams() > AlignArgOffset &&
+ C.Function->getParamDecl(AlignArgOffset)
+ ->getType()
+ ->isAlignValT();
+ return PrefHasAlignArg == CandidateHasAlignArg;
};
- auto IsUnaligned = [&](OverloadCandidate &C) { return !IsAligned(C); };
+ auto IsFallback = [&](OverloadCandidate &C) { return !IsPref(C); };
PrefCands = PrefCandidates->CompleteCandidates(
- S, OCD_AllCandidates, PrefArgs, R.getNameLoc(), IsAligned);
+ S, OCD_AllCandidates, PrefArgs, R.getNameLoc(), IsPref);
Cands = Candidates.CompleteCandidates(S, OCD_AllCandidates, Args,
- R.getNameLoc(), IsUnaligned);
+ R.getNameLoc(), IsFallback);
} else {
Cands = Candidates.CompleteCandidates(S, OCD_AllCandidates, Args,
R.getNameLoc());
@@ -2962,9 +2963,10 @@ bool Sema::FindAllocationFunctions(
QualType AlignValT = Context.VoidTy;
if (isTypeAwareAllocation(OriginalTypeAwareState) ||
- isAlignedAllocation(OriginalAlignedAllocationMode)) {
+ getLangOpts().AlignedAllocation) {
DeclareGlobalNewDelete();
- AlignValT = Context.getCanonicalTagType(getStdAlignValT());
+ if (EnumDecl *StdAlignValT = getStdAlignValT())
+ AlignValT = Context.getCanonicalTagType(StdAlignValT);
}
CXXScalarValueInitExpr Align(AlignValT, nullptr, SourceLocation());
@@ -3048,11 +3050,24 @@ bool Sema::FindAllocationFunctions(
// If no matching function is found and the allocated object type has
// new-extended alignment, the alignment argument is removed from the
// argument list, and overload resolution is performed again.
+ //
+ // C++20 [expr.new]p18:
+ // If no matching function is found then
+ // — if the allocated object type has new-extended alignment, the
+ // alignment argument is removed from the argument list;
+ // — otherwise, an argument that is the type’s alignment and has type
+ // std::align_val_t is added into the argument list immediately
+ // after the first argument;
+ // and then overload resolution is performed again.
+ auto FallbackAlignedAllocationMode = alignedAllocationModeFromBool(
+ !isAlignedAllocation(OriginalAlignedAllocationMode));
+ bool UseFallback = isAlignedAllocation(OriginalAlignedAllocationMode) ||
+ (getLangOpts().CPlusPlus20 &&
+ getLangOpts().AlignedAllocation && getStdAlignValT());
auto FallbackAllocArgs =
- isAlignedAllocation(OriginalAlignedAllocationMode)
- ? FillAllocArgs(TypeAwareAllocationMode::No,
- AlignedAllocationMode::No)
- : ArgsVector();
+ UseFallback ? FillAllocArgs(TypeAwareAllocationMode::No,
+ FallbackAlignedAllocationMode)
+ : ArgsVector();
if (resolveAllocationOverload(*this, R, Range, ResolveMode::Untyped,
AllocArgs, FallbackAllocArgs,
IAP.PassAlignment, OperatorNew,
diff --git a/clang/test/CXX/drs/cwg5xx.cpp b/clang/test/CXX/drs/cwg5xx.cpp
index ea05e714601fc..71793ea1be585 100644
--- a/clang/test/CXX/drs/cwg5xx.cpp
+++ b/clang/test/CXX/drs/cwg5xx.cpp
@@ -1,7 +1,7 @@
// RUN: %clang_cc1 -std=c++98 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,cxx98-11,cxx98-14,cxx98-17,cxx98
// RUN: %clang_cc1 -std=c++11 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,cxx98-11,cxx98-14,cxx98-17,since-cxx11
// RUN: %clang_cc1 -std=c++14 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,cxx98-14,cxx98-17,since-cxx11
-// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,since-cxx17,cxx98-17,since-cxx11
+// RUN: %clang_cc1 -std=c++17 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,since-cxx17,cxx17,cxx98-17,since-cxx11
// RUN: %clang_cc1 -std=c++20 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,since-cxx20,since-cxx17,since-cxx11
// RUN: %clang_cc1 -std=c++23 %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98-23,since-cxx23,since-cxx20,since-cxx17,since-cxx11
// RUN: %clang_cc1 -std=c++2c %s -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx26,since-cxx23,since-cxx20,since-cxx17,since-cxx11
@@ -682,8 +682,9 @@ namespace cwg553 {
// "is looked up in global scope", where it is not visible.
void *p = new (c) int;
// expected-error at -1 {{no matching function for call to 'operator new'}}
- // since-cxx17-note@#cwg5xx-global-operator-new-aligned {{candidate function not viable: no known conversion from 'cwg553_class' to 'std::align_val_t' for 2nd argument}}
+ // cxx17-note@#cwg5xx-global-operator-new-aligned {{candidate function not viable: no known conversion from 'cwg553_class' to 'std::align_val_t' for 2nd argument}}
// expected-note@#cwg5xx-global-operator-new {{candidate function not viable: requires 1 argument, but 2 were provided}}
+ // since-cxx20-note@#cwg5xx-global-operator-new-aligned {{candidate function not viable: requires 2 arguments, but 3 were provided}}
struct namespace_scope {
friend void *operator new(size_t, namespace_scope);
diff --git a/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp b/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
index d0b24c8fe47b7..dd1b63687f308 100644
--- a/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
+++ b/clang/test/CXX/expr/expr.unary/expr.new/p14.cpp
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -std=c++1z -fexceptions %s -verify
+// RUN: %clang_cc1 -std=c++1z -fexceptions %s -verify=expected,cxx17
+// RUN: %clang_cc1 -std=c++20 -fexceptions %s -verify=expected,since-cxx20
using size_t = decltype(sizeof(0));
namespace std { enum class align_val_t : size_t {}; }
@@ -6,7 +7,9 @@ namespace std { enum class align_val_t : size_t {}; }
struct Arg {} arg;
// If the type is aligned, first try with an alignment argument and then
-// without. If not, never consider supplying an alignment.
+// without. If not:
+// - For C++17, never consider supplying an alignment;
+// - For C++20 and later, try without an alignment argument first, then with it.
template<unsigned Align, typename ...Ts>
struct alignas(Align) Unaligned {
@@ -19,11 +22,11 @@ auto *ubp = new (arg) Unaligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2, Arg>; // e
template<unsigned Align, typename ...Ts>
struct alignas(Align) Aligned {
- void *operator new(size_t, std::align_val_t, Ts...) = delete; // expected-note 2{{deleted}} expected-note 2{{not viable}}
+ void *operator new(size_t, std::align_val_t, Ts...) = delete; // cxx17-note 2{{deleted}} cxx17-note 2{{not viable}} since-cxx20-note 4{{deleted}}
};
-auto *aa = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__>; // expected-error {{no matching}}
+auto *aa = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__>; // cxx17-error {{no matching}} since-cxx20-error {{deleted}}
auto *ab = new Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2>; // expected-error {{deleted}}
-auto *aap = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__, Arg>; // expected-error {{no matching}}
+auto *aap = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__, Arg>; // cxx17-error {{no matching}} since-cxx20-error {{deleted}}
auto *abp = new (arg) Aligned<__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2, Arg>; // expected-error {{deleted}}
// If both are available, we prefer the aligned version for an overaligned
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 2a2f91186871e..408cce414264f 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -2,20 +2,21 @@
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++11
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++98 -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++11 -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++14 -fexperimental-new-constant-interpreter -DNEW_INTERP
// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,precxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++17 -fexperimental-new-constant-interpreter -DNEW_INTERP
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 -fexperimental-new-constant-interpreter -DNEW_INTERP
-// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx17,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++20 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx98-23,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++23 -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,since-cxx26,cxx20 %s -triple=i686-pc-linux-gnu -Wno-new-returns-null -std=c++2c -fexperimental-new-constant-interpreter -DNEW_INTERP
// FIXME Location is (frontend)
// cxx17-note@*:* {{candidate function not viable: requires 2 arguments, but 3 were provided}}
+// cxx20-note@*:* {{candidate function not viable: requires 2 arguments, but 4 were provided}}
#include <stddef.h>
diff --git a/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp b/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
index 9c34cb8e0d508..dc6a8ab6471d5 100644
--- a/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
+++ b/clang/test/SemaCXX/std-align-val-t-in-operator-new.cpp
@@ -4,6 +4,8 @@
// RUN: %clang_cc1 -std=c++14 -faligned-allocation -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++17 -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++17 -faligned-allocation -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -std=c++20 -faligned-allocation -fsyntax-only -verify %s
namespace std {
typedef __SIZE_TYPE__ size_t;
@@ -62,7 +64,11 @@ void *operator new(std::size_t, std::align_val_t, X); // #3
// FIXME: Consider improving notes 1 and 3 here to say that these are aligned
// allocation functions and the type is not over-aligned.
X *p = new (123) X; // expected-error {{no matching function}}
+#if __cplusplus >= 202002L
+// expected-note@#1 {{requires 2 arguments, but 4 were provided}}
+#else
// expected-note@#1 {{no known conversion from 'int' to 'std::align_val_t' for 2nd argument}}
+#endif
// expected-note@#2 {{no known conversion from 'int' to 'X' for 2nd argument}}
// expected-note@#3 {{requires 3 arguments}}
// expected-note@* {{requires 1 argument, but 2 were provided}} (builtin)
More information about the llvm-branch-commits
mailing list