https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/197005
>From fb1f6c372ef765bbdb948328a1341418061febdd Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 11 May 2026 19:39:23 +0200 Subject: [PATCH 1/8] [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 c45d54fdd2e88..6947f690d9197 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3873,6 +3873,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 a0894318dbd53..6e7f2645d1545 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15581,3 +15581,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 535adcb2ae109..16d8a4a7728dc 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 98062afae4577..d7c25f3a3c253 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18023,6 +18023,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 96c4ce489fe04..99414d28e940c 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6701,7 +6701,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 a99ad36bc689431fc93470728b05e77b364f4e2a Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 16:52:07 +0200 Subject: [PATCH 2/8] 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 d7c25f3a3c253..51508a5093ccd 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18020,11 +18020,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 99414d28e940c..db471ef8fca17 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6701,7 +6701,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 d94eb4115127dc3c4192b1a2e35ae57b8f2028d7 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 19:22:43 +0200 Subject: [PATCH 3/8] 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 6947f690d9197..951af8a604a85 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3873,8 +3873,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 2b3055d6d6bdd..c548ab2e67d4c 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1619,6 +1619,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 6e7f2645d1545..c5195ba4dcbc2 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15582,8 +15582,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 16d8a4a7728dc..1c23dbca7cd4e 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 51508a5093ccd..e4da73a54d32e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18022,9 +18022,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 db471ef8fca17..e80b17ab6634f 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6703,9 +6703,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 ae7a6aa78f7a06fca3979443b1e064e0c3df4ebe Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:00:59 +0200 Subject: [PATCH 4/8] 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 3844bb95267033e6860b5a61243909bfbe6b6107 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:07:02 +0200 Subject: [PATCH 5/8] 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 c5195ba4dcbc2..6430bfa56159f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15584,8 +15584,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 c66b4ad9bdcdab1bb80270fd293ce6c07a505415 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 20:02:28 +0200 Subject: [PATCH 6/8] 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 6430bfa56159f..c779b72298895 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15584,14 +15584,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 15d7438b5764456904270f3f6866377b2f74ec79 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 18 May 2026 13:50:18 +0200 Subject: [PATCH 7/8] 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 c779b72298895..89f3b3ffca83f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15587,23 +15587,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 3f3a80f5b77a3..1110b67ab15b2 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); } @@ -19450,7 +19450,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 129bcc59870caeac2aab88673b37ec4ef4c6a743 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 19 May 2026 14:49:52 +0200 Subject: [PATCH 8/8] 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 951af8a604a85..b59cc97cdbfd4 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3873,7 +3873,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 89f3b3ffca83f..82cc2fbb93dd3 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15583,8 +15583,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 1c23dbca7cd4e..9cadc893407d5 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 e4da73a54d32e..b77777e9f02ce 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18023,7 +18023,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 e80b17ab6634f..8d533caac676c 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6704,7 +6704,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}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
