https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/197005
>From a57015a0753e896414993a2b0edebbc72bbcec8d Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 11 May 2026 19:39:23 +0200 Subject: [PATCH 01/16] [clang] Allow C-style casts in constexpr in MS compatible mode Patch allows folding constant expression if -fms-compatibility is given and the only problem found by evaluator is C-style cast. This makes it more permissive than MSVC, which treats this expression as constant: ``` (FIELD_OFFSET(S,y) + 3) % 5 ``` but doesn't do the same for this one: ``` (FIELD_OFFSET(S,y) + 3) ``` where FIELD_OFFSET is defined as: ``` ``` --- clang/include/clang/AST/ASTContext.h | 3 + clang/lib/AST/ASTContext.cpp | 6 + clang/lib/AST/Decl.cpp | 8 +- clang/lib/Sema/SemaExpr.cpp | 2 + clang/lib/Sema/SemaOverload.cpp | 3 +- clang/test/SemaCXX/microsoft-constexpr.cpp | 142 +++++++++++++++++++++ 6 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index c952b8d46974c..3f9d57284752a 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,6 +3884,9 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); + bool + shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes); + private: /// All OMPTraitInfo objects live in this collection, one per /// `pragma omp [begin] declare variant` directive. diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index a401a7471e6fc..8cf00713c2a72 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15633,3 +15633,9 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { if (FieldDecl *FD = Comp.getField(); isPFPField(FD)) PFPFieldsWithEvaluatedOffset.insert(FD); } + +bool ASTContext::shouldIgnoreNotesForConstEval( + SmallVectorImpl<PartialDiagnosticAt> &Notes) { + return getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; +} diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index b797ebfa1a7e1..d0876834c913b 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2587,8 +2587,12 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && - !Notes.empty()) - Result = false; + !Notes.empty()) { + if (!Ctx.shouldIgnoreNotesForConstEval(Notes)) + Result = false; + else + Notes.clear(); + } // Ensure the computed APValue is cleaned up later if evaluation succeeded, // or that it's empty (so that there's nothing to clean up) if evaluation diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index ad6e7183cb3a4..0769bc787e886 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,6 +18030,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then // this is a constant expression. + if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) { if (Result) *Result = EvalResult.Val.getInt(); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index e11bbd7085798..768bd294d7b69 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6710,7 +6710,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Result = ExprError(); } else { Value = Eval.Val; - + if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr.cpp b/clang/test/SemaCXX/microsoft-constexpr.cpp new file mode 100644 index 0000000000000..fb0a849e5ce7b --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr.cpp @@ -0,0 +1,142 @@ +// Some of this should fail in MSVC, but work in clang +// when -fms-compatibility is enabled. +// RUN: %clang -fsyntax-only -fms-compatibility -std=c++20 %s + +typedef long LONG; +typedef __int64 LONG_PTR, *PLONG_PTR; + +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +constexpr bool cb_eq = FIELD_OFFSET(S, y) == 4; +constexpr bool cb_ne = FIELD_OFFSET(S, y) != 0; +constexpr bool cb_lt = FIELD_OFFSET(S, y) < 8; +constexpr bool cb_le = FIELD_OFFSET(S, y) <= 4; +constexpr bool cb_gt = FIELD_OFFSET(S, y) > 0; +constexpr bool cb_ge = FIELD_OFFSET(S, y) >= 4; +constexpr bool cb_bool = FIELD_OFFSET(S, y); + +static_assert(FIELD_OFFSET(S, y) == 4); +static_assert(FIELD_OFFSET(S, y) != 0); +static_assert(FIELD_OFFSET(S, y) < 8); +static_assert(FIELD_OFFSET(S, y) <= 4); +static_assert(FIELD_OFFSET(S, y) > 0); +static_assert(FIELD_OFFSET(S, y) >= 4); +static_assert(FIELD_OFFSET(S, y)); + + +enum E { + enum_offset_y = FIELD_OFFSET(S, y), + enum_cmp_y = FIELD_OFFSET(S, y) == 4 +}; + +int arr_bound[FIELD_OFFSET(S, y)]; +int arr_bound_cmp[FIELD_OFFSET(S, y) == 4 ? 1 : -1]; + +struct BitField { + int bf1 : FIELD_OFFSET(S, y); + int bf2 : FIELD_OFFSET(S, y) == 4; +}; + +template<int N> +struct TplInt {}; + +template<bool B> +struct TplBool {}; + +TplInt<FIELD_OFFSET(S, y)> tpl_int; +TplBool<FIELD_OFFSET(S, y) == 4> tpl_bool; +TplBool<FIELD_OFFSET(S, y)> tpl_bool_conv; + +void f() noexcept(FIELD_OFFSET(S, y) == 4) {} + +template<class T> +void g() { + if constexpr (FIELD_OFFSET(S, y) == 4) { + } else { + } +} + +struct ExplicitCtor { + explicit(FIELD_OFFSET(S, y) == 4) ExplicitCtor(int) {} +}; + +alignas(FIELD_OFFSET(S,y)) int __g; + +constinit int constinit_offset = FIELD_OFFSET(S, y); +constinit bool constinit_bool = FIELD_OFFSET(S, y) == 4; + +constexpr int constexpr_offset = FIELD_OFFSET(S, y); +constexpr int constexpr_cmp_as_int = FIELD_OFFSET(S, y) == 4; +constexpr bool constexpr_bool = FIELD_OFFSET(S, y) == 4; + +int switch_test(int v) { + switch (v) { + case FIELD_OFFSET(S, y): + return 1; + case FIELD_OFFSET(S, x): + return 2; + default: + return 0; + } +} + +template<int N = FIELD_OFFSET(S, y)> +struct DefaultTplInt {}; + +template<bool B = FIELD_OFFSET(S, y) == 4> +struct DefaultTplBool {}; + +DefaultTplInt<> default_tpl_int; +DefaultTplBool<> default_tpl_bool; + +struct ArrayMember { + int a[FIELD_OFFSET(S, y)]; +}; + +union U { + char c; + int a[FIELD_OFFSET(S, y)]; +}; + +typedef char typedef_arr[FIELD_OFFSET(S, y)]; +using using_arr = char[FIELD_OFFSET(S, y)]; + +constexpr int ternary_offset = + FIELD_OFFSET(S, y) == 4 ? FIELD_OFFSET(S, y) : -1; + +constexpr bool logical_and = + FIELD_OFFSET(S, y) == 4 && FIELD_OFFSET(S, x) == 0; + +constexpr bool logical_or = + FIELD_OFFSET(S, y) == 4 || FIELD_OFFSET(S, x) == 123; + +constexpr bool logical_not = + !FIELD_OFFSET(S, x); + +constexpr int arithmetic_add = FIELD_OFFSET(S, y) + 1; +constexpr int arithmetic_sub = FIELD_OFFSET(S, y) - 1; +constexpr int arithmetic_mul = FIELD_OFFSET(S, y) * 2; +constexpr int arithmetic_div = FIELD_OFFSET(S, y) / 2; +constexpr int arithmetic_mod = FIELD_OFFSET(S, y) % 3; + +constexpr int bit_or = FIELD_OFFSET(S, y) | 1; +constexpr int bit_and = FIELD_OFFSET(S, y) & 7; +constexpr int bit_xor = FIELD_OFFSET(S, y) ^ 1; +constexpr int bit_shl = FIELD_OFFSET(S, y) << 1; +constexpr int bit_shr = FIELD_OFFSET(S, y) >> 1; + +constexpr int comma_expr = (0, FIELD_OFFSET(S, y)); + +constexpr int cast_int = (int)FIELD_OFFSET(S, y); +constexpr long cast_long = (long)FIELD_OFFSET(S, y); +constexpr bool cast_bool = (bool)FIELD_OFFSET(S, y); + +template<class T, int N> +struct DependentTpl {}; + +DependentTpl<S, FIELD_OFFSET(S, y)> dependent_tpl; >From 875536c0e1d81260c7fc83d2cb48a6f5ada26a47 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 16:52:07 +0200 Subject: [PATCH 02/16] Add SFINAE handling and test cases We don't want relaxed constant folding to happen during template parameter subsititution, to avoid unexpected instantiations. --- clang/lib/Sema/SemaExpr.cpp | 8 ++++++-- clang/lib/Sema/SemaOverload.cpp | 5 ++++- .../SemaCXX/microsoft-constexpr-SFINAE.cpp | 20 +++++++++++++++++++ .../SemaCXX/microsoft-constexpr-SFINAE2.cpp | 20 +++++++++++++++++++ 4 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 0769bc787e886..342ab881f0282 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18027,11 +18027,15 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, if (!isa<ConstantExpr>(E)) E = ConstantExpr::Create(Context, E, EvalResult.Val); + // For -fms-compatibility mode we relax some requirements + // for constant folding in non-SFINAE contexts + if (!isSFINAEContext() && + getASTContext().shouldIgnoreNotesForConstEval(Notes)) + Notes.clear(); + // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then // this is a constant expression. - if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) { if (Result) *Result = EvalResult.Val.getInt(); diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 768bd294d7b69..e71eebfd8f40e 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6710,7 +6710,10 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Result = ExprError(); } else { Value = Eval.Val; - if (getASTContext().shouldIgnoreNotesForConstEval(Notes)) + // For -fms-compatibility mode we relax some requirements + // for constant folding in non-SFINAE contexts + if (!isSFINAEContext() && + getASTContext().shouldIgnoreNotesForConstEval(Notes)) Notes.clear(); if (Notes.empty()) { // It's a constant expression. diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp new file mode 100644 index 0000000000000..271d1bcfad99e --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -triple x86_64-windows-msvc %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +template<class T, LONG_PTR = FIELD_OFFSET(S, y)> +char probe(int); + +template<class> +long probe(...); + +static_assert(sizeof(probe<int>(0)) == sizeof(char), ""); +// expected-error@-1 {{static assertion failed due to requirement 'sizeof (probe<int>(0)) == sizeof(char)'}} +// expected-note@-2 {{expression evaluates to '4 == 1'}} diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp new file mode 100644 index 0000000000000..b63ea73eaab28 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fsyntax-only -fms-compatibility -triple x86_64-windows-msvc -verify %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +template<class T, bool = __builtin_choose_expr(FIELD_OFFSET(T, y) > 0, true, false)> +char probe(int); + +template<class> +long probe(...); + +static_assert(sizeof(probe<S>(0)) == sizeof(char), ""); +// expected-error@-1 {{static assertion failed due to requirement 'sizeof (probe<S>(0)) == sizeof(char)'}} +// expected-note@-2 {{expression evaluates to '4 == 1'}} >From 2337ddc90e5ee019c21c540b562ec281cdd49bb6 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 19:22:43 +0200 Subject: [PATCH 03/16] Add opt-in warning for MS relaxed constant folding --- clang/include/clang/AST/ASTContext.h | 3 +-- clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++++ clang/include/clang/Basic/DiagnosticGroups.td | 2 ++ clang/lib/AST/ASTContext.cpp | 11 ++++++++--- clang/lib/AST/Decl.cpp | 4 +--- clang/lib/Sema/SemaExpr.cpp | 5 ++--- clang/lib/Sema/SemaOverload.cpp | 5 ++--- clang/test/SemaCXX/microsoft-constexpr2.cpp | 12 ++++++++++++ 8 files changed, 32 insertions(+), 14 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr2.cpp diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 3f9d57284752a..0387cef5b6a4d 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,7 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool - shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..b7252902c969e 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1030,6 +1030,10 @@ def warn_npot_ms_struct : Warning< "ms_struct may not produce Microsoft-compatible layouts with fundamental " "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; +def warn_relaxed_constant_fold : Warning< + "folding this constant expression is a Microsoft extension">, + InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore; + def err_itanium_layout_unimplemented : Error< "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">; diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td index 8031f99419bdc..fe2eadd7a3aa4 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1642,6 +1642,8 @@ def MicrosoftStringLiteralFromPredefined : DiagGroup< "microsoft-string-literal-from-predefined">; def MicrosoftInlineOnNonFunction : DiagGroup< "microsoft-inline-on-non-function">; +def MicrosoftRelaxedConstantFold : + DiagGroup<"relaxed-constant-fold">; // Aliases. def : DiagGroup<"msvc-include", [MicrosoftInclude]>; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 8cf00713c2a72..956e000a927c9 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15634,8 +15634,13 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { PFPFieldsWithEvaluatedOffset.insert(FD); } -bool ASTContext::shouldIgnoreNotesForConstEval( +bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - return getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + if (Fold) { + getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); + Notes.clear(); + } + return Fold; } diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index d0876834c913b..987167342e49d 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,10 +2588,8 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.shouldIgnoreNotesForConstEval(Notes)) + if (!Ctx.maybeFoldMSConstexpr(Notes)) Result = false; - else - Notes.clear(); } // Ensure the computed APValue is cleaned up later if evaluation succeeded, diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 342ab881f0282..f39a9e56e6535 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18029,9 +18029,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && - getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); + if (!isSFINAEContext()) + getASTContext().maybeFoldMSConstexpr(Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index e71eebfd8f40e..cf1dbea6c2379 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6712,9 +6712,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Value = Eval.Val; // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && - getASTContext().shouldIgnoreNotesForConstEval(Notes)) - Notes.clear(); + if (!isSFINAEContext()) + getASTContext().maybeFoldMSConstexpr(Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp new file mode 100644 index 0000000000000..25d4b3ed2b3f3 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -0,0 +1,12 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -Wrelaxed-constant-fold %s + +typedef long long LONG_PTR; +typedef long LONG; +#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) + +struct S { + int x; + int y; +}; + +constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}} >From a524d313d5ce09e7c67cc8b6237a3e82f86b712c Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:00:59 +0200 Subject: [PATCH 04/16] Change definition from Warning to Extension --- clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index b7252902c969e..192eb85b019cf 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1030,9 +1030,9 @@ def warn_npot_ms_struct : Warning< "ms_struct may not produce Microsoft-compatible layouts with fundamental " "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; -def warn_relaxed_constant_fold : Warning< +def warn_relaxed_constant_fold : Extension< "folding this constant expression is a Microsoft extension">, - InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore; + InGroup<MicrosoftRelaxedConstantFold>; def err_itanium_layout_unimplemented : Error< >From 03359733c30849c248787df0ae3e051c153a243e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:07:02 +0200 Subject: [PATCH 05/16] Don't allow folding constant expressions using dynamic_cast This can be done with -std=c++20, so we don't need to this in Microsoft compatibility mode. --- clang/include/clang/Basic/PartialDiagnostic.h | 8 ++++++++ clang/lib/AST/ASTContext.cpp | 7 +++++-- clang/test/SemaCXX/microsoft-constexpr3.cpp | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 clang/test/SemaCXX/microsoft-constexpr3.cpp diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h index 4bf6049d08fdb..7658bfa3795f4 100644 --- a/clang/include/clang/Basic/PartialDiagnostic.h +++ b/clang/include/clang/Basic/PartialDiagnostic.h @@ -189,6 +189,14 @@ class PartialDiagnostic : public StreamingDiagnostic { == DiagnosticsEngine::ak_std_string && "Not a string arg"); return DiagStorage->DiagArgumentsStr[I]; } + uint64_t getValueArg(unsigned I) { + assert(DiagStorage && "No diagnostic storage?"); + assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); + assert(DiagStorage->DiagArgumentsKind[I] != + DiagnosticsEngine::ak_std_string && + "Not an integer arg"); + return DiagStorage->DiagArgumentsVal[I]; + } }; inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 956e000a927c9..15099a3814e5c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15636,8 +15636,11 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast; + bool Fold = + getLangOpts().MSVCCompat && Notes.size() == 1 && + Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast && + Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic; + if (Fold) { getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); Notes.clear(); diff --git a/clang/test/SemaCXX/microsoft-constexpr3.cpp b/clang/test/SemaCXX/microsoft-constexpr3.cpp new file mode 100644 index 0000000000000..dd52bb8f81d32 --- /dev/null +++ b/clang/test/SemaCXX/microsoft-constexpr3.cpp @@ -0,0 +1,18 @@ +// Ignore dynamic_cast when relaxing constant expression with -fms-compatibility +// However using dynamic_cast is still possible in c++20 and higher +// RUN: not %clang_cc1 -std=c++11 -fms-compatibility -fsyntax-only %s +// RUN: %clang_cc1 -std=c++20 -fms-compatibility -fsyntax-only %s + +struct B { + virtual ~B() {} +}; + +struct D : B { + int x = 123; +}; + +#define IsD(x) (dynamic_cast<const D*>(x) != 0) + +static const D od; + +constexpr bool is_d = IsD(&od); >From 2263d9f615579c9f3638e8f91c6b9ab5e127c51c Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 20:02:28 +0200 Subject: [PATCH 06/16] Improved diagnostics --- .../include/clang/Basic/DiagnosticASTKinds.td | 5 ++-- clang/include/clang/Basic/PartialDiagnostic.h | 2 +- clang/lib/AST/ASTContext.cpp | 30 +++++++++++++------ clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 192eb85b019cf..7f0f0a503a8ba 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1031,10 +1031,11 @@ def warn_npot_ms_struct : Warning< "data types with sizes that aren't a power of two">, DefaultError, InGroup<IncompatibleMSStruct>; def warn_relaxed_constant_fold : Extension< - "folding this constant expression is a Microsoft extension">, + "folding constant expression involving " + "%select{reinterpret_cast|cast that performs the conversions of a reinterpret_cast}0" + " is a Microsoft extension">, InGroup<MicrosoftRelaxedConstantFold>; - def err_itanium_layout_unimplemented : Error< "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">; diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h index 7658bfa3795f4..7469e45f7d888 100644 --- a/clang/include/clang/Basic/PartialDiagnostic.h +++ b/clang/include/clang/Basic/PartialDiagnostic.h @@ -194,7 +194,7 @@ class PartialDiagnostic : public StreamingDiagnostic { assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); assert(DiagStorage->DiagArgumentsKind[I] != DiagnosticsEngine::ak_std_string && - "Not an integer arg"); + "Not a value arg"); return DiagStorage->DiagArgumentsVal[I]; } }; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 15099a3814e5c..38f292987722f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15636,14 +15636,26 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { bool ASTContext::maybeFoldMSConstexpr( SmallVectorImpl<PartialDiagnosticAt> &Notes) { - bool Fold = - getLangOpts().MSVCCompat && Notes.size() == 1 && - Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast && - Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic; - - if (Fold) { - getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold); - Notes.clear(); + if (Notes.size() != 1 || !getLangOpts().MSVCCompat) + return false; + auto &PD = Notes[0].second; + if (PD.getDiagID() != diag::note_constexpr_invalid_cast) + return false; + unsigned CastID; + switch (PD.getValueArg(0)) { + case diag::ConstexprInvalidCastKind::Reinterpret: + CastID = 0; + break; + case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret: + if (!PD.getValueArg(1)) + return false; + CastID = 1; + break; + default: + return false; } - return Fold; + getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) + << CastID; + Notes.clear(); + return true; } diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 25d4b3ed2b3f3..e756a0240d5fe 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -3,10 +3,12 @@ typedef long long LONG_PTR; typedef long LONG; #define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field)) +#define FIELD_OFFSET2(type, field) (reinterpret_cast<LONG_PTR>(&(((type *)0)->field))) struct S { int x; int y; }; -constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}} +constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} +constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} >From 8b83a3dedfb359471ffeb1589c1c327d3ea79fe9 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 18 May 2026 13:50:18 +0200 Subject: [PATCH 07/16] Add note to distinguish ptr to int conversions --- .../include/clang/Basic/DiagnosticASTKinds.td | 4 ++++ clang/lib/AST/ASTContext.cpp | 18 +++--------------- clang/lib/AST/ExprConstant.cpp | 4 ++-- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 7f0f0a503a8ba..ce864bdb1fe8a 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -16,6 +16,10 @@ def note_constexpr_invalid_cast : Note< "of a reinterpret_cast}1}|%CastFrom{cast from %1}}0" " is not allowed in a constant expression" "%select{| in C++ standards before C++20||}0">; +def note_constexpr_invalid_cast_ptrtoint : Note< + "%select{reinterpret_cast||" + "%select{this conversion|cast that performs the conversions of a reinterpret_cast}1|" + "}0 is not allowed in a constant expression">; def note_constexpr_invalid_void_star_cast : Note< "cast from %0 is not allowed in a constant expression " "%select{in C++ standards before C++2c|because the pointed object " diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 38f292987722f..55effa4e2397e 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15639,23 +15639,11 @@ bool ASTContext::maybeFoldMSConstexpr( if (Notes.size() != 1 || !getLangOpts().MSVCCompat) return false; auto &PD = Notes[0].second; - if (PD.getDiagID() != diag::note_constexpr_invalid_cast) + if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) return false; - unsigned CastID; - switch (PD.getValueArg(0)) { - case diag::ConstexprInvalidCastKind::Reinterpret: - CastID = 0; - break; - case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret: - if (!PD.getValueArg(1)) - return false; - CastID = 1; - break; - default: - return false; - } getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) - << CastID; + << !!PD.getValueArg(0); Notes.clear(); + return true; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index b1cea63a8ede6..75d268df7bf10 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8533,7 +8533,7 @@ class ExprEvaluatorBase } bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) { - CCEDiag(E, diag::note_constexpr_invalid_cast) + CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) << diag::ConstexprInvalidCastKind::Reinterpret; return static_cast<Derived*>(this)->VisitCastExpr(E); } @@ -19484,7 +19484,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { } case CK_PointerToIntegral: { - CCEDiag(E, diag::note_constexpr_invalid_cast) + CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret << Info.Ctx.getLangOpts().CPlusPlus << E->getSourceRange(); >From 361c530c42e5db9a453868541d7e6549243be230 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 19 May 2026 14:49:52 +0200 Subject: [PATCH 08/16] Don't allow LValue casts for constexpr --- clang/include/clang/AST/ASTContext.h | 3 ++- clang/lib/AST/ASTContext.cpp | 4 ++-- clang/lib/AST/Decl.cpp | 2 +- clang/lib/Sema/SemaExpr.cpp | 2 +- clang/lib/Sema/SemaOverload.cpp | 2 +- clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 +++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 0387cef5b6a4d..8a1f55350b0ca 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,7 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldMSConstexpr(APValue &Val, + SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 55effa4e2397e..01a14f0ac2cb9 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15635,8 +15635,8 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { } bool ASTContext::maybeFoldMSConstexpr( - SmallVectorImpl<PartialDiagnosticAt> &Notes) { - if (Notes.size() != 1 || !getLangOpts().MSVCCompat) + APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { + if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) return false; auto &PD = Notes[0].second; if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 987167342e49d..c96982d83c1d7 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,7 +2588,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldMSConstexpr(Notes)) + if (!Ctx.maybeFoldMSConstexpr(Eval->Evaluated, Notes)) Result = false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index f39a9e56e6535..b114d30977600 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,7 +18030,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Notes); + getASTContext().maybeFoldMSConstexpr(EvalResult.Val, Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index cf1dbea6c2379..575add96a51ea 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6713,7 +6713,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Notes); + getASTContext().maybeFoldMSConstexpr(Value, Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index e756a0240d5fe..ad4faddc83b1d 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -8,7 +8,9 @@ typedef long LONG; struct S { int x; int y; -}; +} ob; constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} +constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From a5c2ebb979f9191fc09166d422b3933db3955546 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 11:46:54 +0200 Subject: [PATCH 09/16] Rename a function The name MSConstexpr is already taken by class handling [[msvc::constexpr]] attribute --- clang/include/clang/AST/ASTContext.h | 4 ++-- clang/lib/AST/ASTContext.cpp | 2 +- clang/lib/AST/Decl.cpp | 2 +- clang/lib/Sema/SemaExpr.cpp | 2 +- clang/lib/Sema/SemaOverload.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 8a1f55350b0ca..71a167103d991 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldMSConstexpr(APValue &Val, - SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool maybeFoldConstexprWithCast(APValue &Val, + SmallVectorImpl<PartialDiagnosticAt> &Notes); private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 01a14f0ac2cb9..3eea213e0a2d5 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15634,7 +15634,7 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { PFPFieldsWithEvaluatedOffset.insert(FD); } -bool ASTContext::maybeFoldMSConstexpr( +bool ASTContext::maybeFoldConstexprWithCast( APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) return false; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index c96982d83c1d7..ad33b1d30fc07 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2588,7 +2588,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldMSConstexpr(Eval->Evaluated, Notes)) + if (!Ctx.maybeFoldConstexprWithCast(Eval->Evaluated, Notes)) Result = false; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index b114d30977600..7cb17fb9bf5b4 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18030,7 +18030,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(EvalResult.Val, Notes); + getASTContext().maybeFoldConstexprWithCast(EvalResult.Val, Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 575add96a51ea..bcb9b427ee123 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6713,7 +6713,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts if (!isSFINAEContext()) - getASTContext().maybeFoldMSConstexpr(Value, Notes); + getASTContext().maybeFoldConstexprWithCast(Value, Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); >From b369e5c18540c6c2c3ca5949e529489534da64c7 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 16:03:26 +0200 Subject: [PATCH 10/16] Fix bugs in evaluator - Correctly emit note, when handling reinterpret_cast - Don't try to attempt folding constexpr having reinterpret_cast, if evaluator has failed (APValue is None). --- clang/lib/AST/Decl.cpp | 2 +- clang/lib/AST/ExprConstant.cpp | 4 +++- clang/test/SemaCXX/microsoft-constexpr2.cpp | 6 ++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index ad33b1d30fc07..65b1cac985f6f 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2584,7 +2584,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, // a constant initializer if we produced notes. In that case, we can't keep // the result, because it may only be correct under the assumption that the // initializer is a constant context. - if (IsConstantInitialization && + if (Result && IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 75d268df7bf10..5326a8b3d4880 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8533,7 +8533,9 @@ class ExprEvaluatorBase } bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) { - CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint) + bool IsPtrToInt = E->getCastKind() == CK_PointerToIntegral; + CCEDiag(E, IsPtrToInt ? diag::note_constexpr_invalid_cast_ptrtoint + : diag::note_constexpr_invalid_cast) << diag::ConstexprInvalidCastKind::Reinterpret; return static_cast<Derived*>(this)->VisitCastExpr(E); } diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index ad4faddc83b1d..c28e0f3d17574 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -14,3 +14,9 @@ constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant ex constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}} // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr int* b4 = reinterpret_cast<int*>(&ob); // expected-error {{constexpr variable 'b4' must be initialized by a constant expression}} + // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{constexpr variable 'b5' must be initialized by a constant expression}} + (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} + // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} >From 78cbaa116620ba07ecb798adfe8f14f8ddab7a56 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 15:20:17 +0200 Subject: [PATCH 11/16] Don't allow constexprs having a cast to contain an l-value --- clang/include/clang/AST/ASTContext.h | 4 ++-- clang/include/clang/AST/Expr.h | 4 ++++ clang/lib/AST/ASTContext.cpp | 4 ++-- clang/lib/AST/Decl.cpp | 5 ++--- clang/lib/AST/ExprConstant.cpp | 10 +++++++--- clang/lib/Sema/SemaExpr.cpp | 4 ++-- clang/lib/Sema/SemaOverload.cpp | 4 ++-- clang/test/SemaCXX/microsoft-constexpr2.cpp | 2 ++ 8 files changed, 23 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 71a167103d991..884ec336090fc 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,8 +3884,8 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool maybeFoldConstexprWithCast(APValue &Val, - SmallVectorImpl<PartialDiagnosticAt> &Notes); + bool + maybeFoldConstexprWithCast(SmallVectorImpl<PartialDiagnosticAt> &Notes) const; private: /// All OMPTraitInfo objects live in this collection, one per diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index b91bf4a5375fb..d786a8964b7f8 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -619,6 +619,10 @@ class Expr : public ValueStmt { /// Likewise, INT_MAX + 1 can be folded to INT_MIN, but has UB. bool HasUndefinedBehavior = false; + /// Whether part of expression is an LValue. + /// Used when evaluating constant expression with Microsoft extensions. + bool HasLValue = false; + /// Diag - If this is non-null, it will be filled in with a stack of notes /// indicating why evaluation failed (or why it failed to produce a constant /// expression). diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 3eea213e0a2d5..dc0fdea473645 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15635,8 +15635,8 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { } bool ASTContext::maybeFoldConstexprWithCast( - APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) { - if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue()) + SmallVectorImpl<PartialDiagnosticAt> &Notes) const { + if (Notes.size() != 1 || !getLangOpts().MSVCCompat) return false; auto &PD = Notes[0].second; if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 65b1cac985f6f..d880924e2d6ab 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2584,12 +2584,11 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes, // a constant initializer if we produced notes. In that case, we can't keep // the result, because it may only be correct under the assumption that the // initializer is a constant context. - if (Result && IsConstantInitialization && + if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && !Notes.empty()) { - if (!Ctx.maybeFoldConstexprWithCast(Eval->Evaluated, Notes)) - Result = false; + Result = false; } // Ensure the computed APValue is cleaned up later if evaluation succeeded, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 5326a8b3d4880..0f8e2c6b45266 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -19495,6 +19495,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { return false; if (LV.getLValueBase()) { + Info.EvalStatus.HasLValue = true; // Only allow based lvalue casts if they are lossless. // FIXME: Allow a larger integer size than the pointer size, and allow // narrowing back down to pointer width in subsequent integral casts. @@ -21543,9 +21544,12 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, llvm_unreachable("Unhandled cleanup; missing full expression marker?"); } - return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, - ConstantExprKind::Normal) && - CheckMemoryLeaks(Info); + bool Checked = CheckConstantExpression(Info, DeclLoc, DeclTy, Value, + ConstantExprKind::Normal) && + CheckMemoryLeaks(Info); + if (Checked && !Info.EvalStatus.HasLValue) + Ctx.maybeFoldConstexprWithCast(Notes); + return Checked; } bool VarDecl::evaluateDestruction( diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 7cb17fb9bf5b4..35951d66c58d2 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18029,8 +18029,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext()) - getASTContext().maybeFoldConstexprWithCast(EvalResult.Val, Notes); + if (!isSFINAEContext() && !EvalResult.HasLValue) + getASTContext().maybeFoldConstexprWithCast(Notes); // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index bcb9b427ee123..f4bf26ee85fb6 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6712,8 +6712,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Value = Eval.Val; // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext()) - getASTContext().maybeFoldConstexprWithCast(Value, Notes); + if (!isSFINAEContext() && !Eval.HasLValue) + getASTContext().maybeFoldConstexprWithCast(Notes); if (Notes.empty()) { // It's a constant expression. Expr *E = Result.get(); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index c28e0f3d17574..6aff24aa36a44 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -20,3 +20,5 @@ constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{co (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} +constexpr long b7[2] = { FIELD_OFFSET(S, y), (long)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From c7a98611a1d88464ccbdfd51b0caf733332aeb0e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 21:35:51 +0200 Subject: [PATCH 12/16] Fix failing test on Windows --- clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 6aff24aa36a44..fd83b08ce9a3d 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -20,5 +20,5 @@ constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{co (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} -constexpr long b7[2] = { FIELD_OFFSET(S, y), (long)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} - // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b7[2] = { FIELD_OFFSET(S, y), (LONG_PTR)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From 835fede090c43ad58077cdc5197ea9b87f4e7992 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 1 Jun 2026 14:41:39 +0200 Subject: [PATCH 13/16] Limit notes passed during constexpr evaluation This patch permits only note_constexpr_invalid_cast_ptrtoint followed by note_constexpr_null_subobject to be ignored by the constexpr evaluator in -fms-compatibility mode. This restricts the range of allowed expressions to offsetof-like cases only. --- clang/lib/AST/ByteCode/State.cpp | 18 ++++++++++++++++++ clang/lib/AST/ByteCode/State.h | 2 ++ clang/test/SemaCXX/microsoft-constexpr2.cpp | 2 ++ 3 files changed, 22 insertions(+) diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 00e3b1a331172..75d8fc4fefb1a 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -18,6 +18,23 @@ using namespace clang::interp; State::~State() {} +// With -fms-compatibility we allow pointer to integer casts +// followed by nullptr casts. +void State::clearDiagIfNeeded(diag::kind DiagId) { + switch (DiagId) { + case diag::note_constexpr_invalid_cast_ptrtoint: + case diag::note_constexpr_null_subobject: + return; + } + + auto *Diag = EvalStatus.Diag; + if (!Ctx.getLangOpts().MSVCCompat || !Diag || Diag->size() != 1 || + (*Diag)[0].second.getDiagID() != + diag::note_constexpr_invalid_cast_ptrtoint) + return; + Diag->clear(); +} + OptionalDiagnostic State::FFDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { return diag(Loc, DiagId, ExtraNotes, false); @@ -41,6 +58,7 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { + clearDiagIfNeeded(DiagId); // Don't override a previous diagnostic. Don't bother collecting // diagnostics if we're evaluating for overflow. if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) { diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index a720033c7914b..289c024f417e3 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -93,6 +93,8 @@ class State { ASTContext &getASTContext() const { return Ctx; } const LangOptions &getLangOpts() const { return Ctx.getLangOpts(); } + void clearDiagIfNeeded(diag::kind DiagId); + /// Note that we have had a side-effect, and determine whether we should /// keep evaluating. bool noteSideEffect() const { diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index fd83b08ce9a3d..1b917e635d66f 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -22,3 +22,5 @@ constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{co // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b7[2] = { FIELD_OFFSET(S, y), (LONG_PTR)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b8 = (LONG_PTR)((char*)1 + FIELD_OFFSET(S, y)); // expected-error {{constexpr variable 'b8' must be initialized by a constant expression}} + // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} >From 7d02030ac9dba3658a9dc807f7c044328b825010 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Wed, 3 Jun 2026 10:00:08 +0200 Subject: [PATCH 14/16] Refactor and add comments --- clang/lib/AST/ASTContext.cpp | 10 ++++++++-- clang/lib/AST/ByteCode/State.cpp | 19 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index dc0fdea473645..c574942f00d5a 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15634,9 +15634,16 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { PFPFieldsWithEvaluatedOffset.insert(FD); } +// MSVC permits certain C-style casts in constant expressions. +// A common example is FIELD_OFFSET, implemented as +// (LONG_PTR)(&((type*)0)->field). If MSVC compatibility is enabled +// and the only constexpr evaluation failure is a ptr-to-int cast, +// allow folding the expression and optionally emit a warning. bool ASTContext::maybeFoldConstexprWithCast( SmallVectorImpl<PartialDiagnosticAt> &Notes) const { - if (Notes.size() != 1 || !getLangOpts().MSVCCompat) + if (!getLangOpts().MSVCCompat) + return false; + if (Notes.size() != 1) return false; auto &PD = Notes[0].second; if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) @@ -15644,6 +15651,5 @@ bool ASTContext::maybeFoldConstexprWithCast( getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) << !!PD.getValueArg(0); Notes.clear(); - return true; } diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 75d8fc4fefb1a..9f5c209845922 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -18,9 +18,22 @@ using namespace clang::interp; State::~State() {} -// With -fms-compatibility we allow pointer to integer casts -// followed by nullptr casts. +// In MSVC compatibility mode we relax constexpr evaluation for the +// FIELD_OFFSET-style pattern: +// +// (LONG_PTR)(&((T*)0)->field) +// +// Evaluation of such expressions first produces a ptr-to-int cast +// diagnostic and may then encounter a null-subobject access while +// forming the field address. Both diagnostics are considered part of +// the same accepted pattern and should not prevent constant folding. +// +// If a ptr-to-int cast diagnostic was recorded but evaluation later +// reaches any other failure, discard the recorded diagnostic so the +// expression is rejected. void State::clearDiagIfNeeded(diag::kind DiagId) { + if (!Ctx.getLangOpts().MSVCCompat) + return; switch (DiagId) { case diag::note_constexpr_invalid_cast_ptrtoint: case diag::note_constexpr_null_subobject: @@ -28,7 +41,7 @@ void State::clearDiagIfNeeded(diag::kind DiagId) { } auto *Diag = EvalStatus.Diag; - if (!Ctx.getLangOpts().MSVCCompat || !Diag || Diag->size() != 1 || + if (!Diag || Diag->size() != 1 || (*Diag)[0].second.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) return; >From 9d1e914f093c3acd4482e8fab4e39019901042c6 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 5 Jun 2026 17:34:41 +0200 Subject: [PATCH 15/16] Change how we evaluate expressions under -fms-compatibility 1. Ignore invalid_cast_ptrtoint and null_subobject notes in MS compatibility mode, because they're not considered an error. 2. LValue casts are treated as an error, so separate note is emitted for this. After seeing it frontend naturally emits an error. 3. In SFINAE context we simply report evaluation error, so emitting note is not needed. --- clang/include/clang/AST/ASTContext.h | 3 -- clang/include/clang/AST/Expr.h | 10 ++++-- .../include/clang/Basic/DiagnosticASTKinds.td | 5 +-- clang/lib/AST/ASTContext.cpp | 20 ------------ clang/lib/AST/ByteCode/State.cpp | 32 ++++--------------- clang/lib/AST/ByteCode/State.h | 2 +- clang/lib/AST/ExprConstant.cpp | 23 ++++++++----- clang/lib/Sema/SemaExpr.cpp | 4 +-- clang/lib/Sema/SemaOverload.cpp | 6 ++-- clang/test/SemaCXX/microsoft-constexpr.cpp | 1 - clang/test/SemaCXX/microsoft-constexpr2.cpp | 18 ++++++++--- 11 files changed, 51 insertions(+), 73 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 884ec336090fc..c952b8d46974c 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3884,9 +3884,6 @@ OPT_LIST(V) void recordMemberDataPointerEvaluation(const ValueDecl *VD); void recordOffsetOfEvaluation(const OffsetOfExpr *E); - bool - maybeFoldConstexprWithCast(SmallVectorImpl<PartialDiagnosticAt> &Notes) const; - private: /// All OMPTraitInfo objects live in this collection, one per /// `pragma omp [begin] declare variant` directive. diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index d786a8964b7f8..70480cbb84a71 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -619,9 +619,13 @@ class Expr : public ValueStmt { /// Likewise, INT_MAX + 1 can be folded to INT_MIN, but has UB. bool HasUndefinedBehavior = false; - /// Whether part of expression is an LValue. - /// Used when evaluating constant expression with Microsoft extensions. - bool HasLValue = false; + /// Whether we've seen a ptr to int cast or null subobject while evaluating + /// constant expression in MS compatibility mode. + bool SeenCastOrNull = false; + + /// Whether the expression being evaluated is converted from some other + /// expression. This is used to suppress duplicate warnings + bool IsConvertedExpr = false; /// Diag - If this is non-null, it will be filled in with a stack of notes /// indicating why evaluation failed (or why it failed to produce a constant diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index ce864bdb1fe8a..adec7cf07f58f 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -20,6 +20,7 @@ def note_constexpr_invalid_cast_ptrtoint : Note< "%select{reinterpret_cast||" "%select{this conversion|cast that performs the conversions of a reinterpret_cast}1|" "}0 is not allowed in a constant expression">; +def note_constexpr_has_lvalue : Note<"constant expression contains l-value">; def note_constexpr_invalid_void_star_cast : Note< "cast from %0 is not allowed in a constant expression " "%select{in C++ standards before C++2c|because the pointed object " @@ -1036,8 +1037,8 @@ def warn_npot_ms_struct : Warning< DefaultError, InGroup<IncompatibleMSStruct>; def warn_relaxed_constant_fold : Extension< "folding constant expression involving " - "%select{reinterpret_cast|cast that performs the conversions of a reinterpret_cast}0" - " is a Microsoft extension">, + "cast that performs the conversions of a reinterpret_cast " + "is a Microsoft extension">, InGroup<MicrosoftRelaxedConstantFold>; def err_itanium_layout_unimplemented : Error< diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index c574942f00d5a..a401a7471e6fc 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15633,23 +15633,3 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) { if (FieldDecl *FD = Comp.getField(); isPFPField(FD)) PFPFieldsWithEvaluatedOffset.insert(FD); } - -// MSVC permits certain C-style casts in constant expressions. -// A common example is FIELD_OFFSET, implemented as -// (LONG_PTR)(&((type*)0)->field). If MSVC compatibility is enabled -// and the only constexpr evaluation failure is a ptr-to-int cast, -// allow folding the expression and optionally emit a warning. -bool ASTContext::maybeFoldConstexprWithCast( - SmallVectorImpl<PartialDiagnosticAt> &Notes) const { - if (!getLangOpts().MSVCCompat) - return false; - if (Notes.size() != 1) - return false; - auto &PD = Notes[0].second; - if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint) - return false; - getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold) - << !!PD.getValueArg(0); - Notes.clear(); - return true; -} diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 9f5c209845922..286f28d6f06aa 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -18,34 +18,17 @@ using namespace clang::interp; State::~State() {} -// In MSVC compatibility mode we relax constexpr evaluation for the -// FIELD_OFFSET-style pattern: -// -// (LONG_PTR)(&((T*)0)->field) -// -// Evaluation of such expressions first produces a ptr-to-int cast -// diagnostic and may then encounter a null-subobject access while -// forming the field address. Both diagnostics are considered part of -// the same accepted pattern and should not prevent constant folding. -// -// If a ptr-to-int cast diagnostic was recorded but evaluation later -// reaches any other failure, discard the recorded diagnostic so the -// expression is rejected. -void State::clearDiagIfNeeded(diag::kind DiagId) { +bool State::shouldRelaxDiag(diag::kind DiagId) { if (!Ctx.getLangOpts().MSVCCompat) - return; + return false; switch (DiagId) { case diag::note_constexpr_invalid_cast_ptrtoint: case diag::note_constexpr_null_subobject: - return; + EvalStatus.SeenCastOrNull = true; + return true; + default: + return false; } - - auto *Diag = EvalStatus.Diag; - if (!Diag || Diag->size() != 1 || - (*Diag)[0].second.getDiagID() != - diag::note_constexpr_invalid_cast_ptrtoint) - return; - Diag->clear(); } OptionalDiagnostic State::FFDiag(SourceLocation Loc, diag::kind DiagId, @@ -71,7 +54,6 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { - clearDiagIfNeeded(DiagId); // Don't override a previous diagnostic. Don't bother collecting // diagnostics if we're evaluating for overflow. if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) { @@ -115,7 +97,7 @@ PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) { OptionalDiagnostic State::diag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes, bool IsCCEDiag) { - if (EvalStatus.Diag) { + if (EvalStatus.Diag && !shouldRelaxDiag(DiagId)) { if (hasPriorDiagnostic()) { return OptionalDiagnostic(); } diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 289c024f417e3..fd7a1b460de50 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -93,7 +93,7 @@ class State { ASTContext &getASTContext() const { return Ctx; } const LangOptions &getLangOpts() const { return Ctx.getLangOpts(); } - void clearDiagIfNeeded(diag::kind DiagId); + bool shouldRelaxDiag(diag::kind DiagId); /// Note that we have had a side-effect, and determine whether we should /// keep evaluating. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 0f8e2c6b45266..3e7d94f5300ac 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2427,6 +2427,14 @@ static bool CheckLiteralType(EvalInfo &Info, const Expr *E, return false; } +static void CheckMicrosoftRelaxations(EvalInfo &Info, + const SourceLocation &Loc) { + auto *Diag = Info.EvalStatus.Diag; + if (Diag && Diag->empty() && Info.EvalStatus.SeenCastOrNull && + !Info.EvalStatus.IsConvertedExpr) + Info.report(Loc, diag::warn_relaxed_constant_fold); +} + static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, EvalInfo &Info, SourceLocation DiagLoc, QualType Type, const APValue &Value, @@ -2515,6 +2523,9 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, CERK == CheckEvaluationResultKind::ConstantExpression) return CheckMemberPointerConstantExpression(Info, DiagLoc, Type, Value, Kind); + // Emit warning if expression is not LValue, member pointer, + // and contains C-style casts under -fms-compatibility + CheckMicrosoftRelaxations(Info, DiagLoc); // Everything else is fine. return true; } @@ -19495,7 +19506,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) { return false; if (LV.getLValueBase()) { - Info.EvalStatus.HasLValue = true; + CCEDiag(E, diag::note_constexpr_has_lvalue) << E->getSourceRange(); // Only allow based lvalue casts if they are lossless. // FIXME: Allow a larger integer size than the pointer size, and allow // narrowing back down to pointer width in subsequent integral casts. @@ -21475,7 +21486,6 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, // destruction. return false; } - return true; } @@ -21544,12 +21554,9 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, llvm_unreachable("Unhandled cleanup; missing full expression marker?"); } - bool Checked = CheckConstantExpression(Info, DeclLoc, DeclTy, Value, - ConstantExprKind::Normal) && - CheckMemoryLeaks(Info); - if (Checked && !Info.EvalStatus.HasLValue) - Ctx.maybeFoldConstexprWithCast(Notes); - return Checked; + return CheckConstantExpression(Info, DeclLoc, DeclTy, Value, + ConstantExprKind::Normal) && + CheckMemoryLeaks(Info); } bool VarDecl::evaluateDestruction( diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 35951d66c58d2..86db1691b7aa9 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18029,8 +18029,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && !EvalResult.HasLValue) - getASTContext().maybeFoldConstexprWithCast(Notes); + if (isSFINAEContext() && EvalResult.SeenCastOrNull) + Folded = false; // In C++11, we can rely on diagnostics being produced for any expression // which is not a constant expression. If no diagnostics were produced, then diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index f4bf26ee85fb6..fe9a82aa97dad 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6692,6 +6692,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, SmallVector<PartialDiagnosticAt, 8> Notes; Expr::EvalResult Eval; Eval.Diag = &Notes; + Eval.IsConvertedExpr = true; assert(CCE != CCEKind::TempArgStrict && "unnexpected CCE Kind"); @@ -6712,9 +6713,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, Value = Eval.Val; // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (!isSFINAEContext() && !Eval.HasLValue) - getASTContext().maybeFoldConstexprWithCast(Notes); - if (Notes.empty()) { + bool CantFold = isSFINAEContext() && Eval.SeenCastOrNull; + if (Notes.empty() && !CantFold) { // It's a constant expression. Expr *E = Result.get(); if (const auto *CE = dyn_cast<ConstantExpr>(E)) { diff --git a/clang/test/SemaCXX/microsoft-constexpr.cpp b/clang/test/SemaCXX/microsoft-constexpr.cpp index fb0a849e5ce7b..a473e51a55f4c 100644 --- a/clang/test/SemaCXX/microsoft-constexpr.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr.cpp @@ -50,7 +50,6 @@ struct TplBool {}; TplInt<FIELD_OFFSET(S, y)> tpl_int; TplBool<FIELD_OFFSET(S, y) == 4> tpl_bool; -TplBool<FIELD_OFFSET(S, y)> tpl_bool_conv; void f() noexcept(FIELD_OFFSET(S, y) == 4) {} diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 1b917e635d66f..8a1aee4dc8bcb 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -10,17 +10,25 @@ struct S { int y; } ob; + +template<bool B> +struct TplBool {}; + +TplBool<FIELD_OFFSET(S, y)> tc; // expected-error {{non-type template argument evaluates to 4, which cannot be narrowed to type 'bool'}} + // expected-warning@-1 {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} -constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}} +constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}} - // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} + // expected-note@-1 {{constant expression contains l-value}} constexpr int* b4 = reinterpret_cast<int*>(&ob); // expected-error {{constexpr variable 'b4' must be initialized by a constant expression}} // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} constexpr LONG_PTR b5 = (42 - FIELD_OFFSET(S, y)) + // expected-error {{constexpr variable 'b5' must be initialized by a constant expression}} - (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} + (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note {{constant expression contains l-value}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} - // expected-note@-1 {{reinterpret_cast is not allowed in a constant expression}} + // expected-note@-1 {{constant expression contains l-value}} constexpr LONG_PTR b7[2] = { FIELD_OFFSET(S, y), (LONG_PTR)&ob }; // expected-error {{constexpr variable 'b7' must be initialized by a constant expression}} - // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} + // expected-note@-1 {{constant expression contains l-value}} constexpr LONG_PTR b8 = (LONG_PTR)((char*)1 + FIELD_OFFSET(S, y)); // expected-error {{constexpr variable 'b8' must be initialized by a constant expression}} // expected-note@-1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}} +constexpr LONG_PTR b9 = (LONG_PTR)(FIELD_OFFSET(S, y) / 0); // expected-error {{constexpr variable 'b9' must be initialized by a constant expression}} + // expected-note@-1 {{division by zero}} >From 19bf57e9d6fd74b8e992c44499123e5691b8afbd Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 5 Jun 2026 17:58:10 +0200 Subject: [PATCH 16/16] Remove unused code --- clang/include/clang/Basic/PartialDiagnostic.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h index 7469e45f7d888..4bf6049d08fdb 100644 --- a/clang/include/clang/Basic/PartialDiagnostic.h +++ b/clang/include/clang/Basic/PartialDiagnostic.h @@ -189,14 +189,6 @@ class PartialDiagnostic : public StreamingDiagnostic { == DiagnosticsEngine::ak_std_string && "Not a string arg"); return DiagStorage->DiagArgumentsStr[I]; } - uint64_t getValueArg(unsigned I) { - assert(DiagStorage && "No diagnostic storage?"); - assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args"); - assert(DiagStorage->DiagArgumentsKind[I] != - DiagnosticsEngine::ak_std_string && - "Not a value arg"); - return DiagStorage->DiagArgumentsVal[I]; - } }; inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB, _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
