https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/197005
>From b976e721db52309359dbf159ae3d41aa1da30430 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 11 May 2026 19:39:23 +0200 Subject: [PATCH 01/21] [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 7ed6509c3c16c..e4042876b4bbb 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,6 +3904,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 2228811546c0f..8c20c95c3bcaa 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15698,3 +15698,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 4eaef0d87f3e5..d76365e59fb35 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2590,8 +2590,12 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && - EStatus.DiagEmitted) - Result = false; + EStatus.DiagEmitted) { + 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 b844670543a55..59ff6f4dff7c6 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18125,6 +18125,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 eafda32198f11..47019876b7233 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6726,7 +6726,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 20066042467a48cd80b14bbd95063ba65b2ec13a Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 16:52:07 +0200 Subject: [PATCH 02/21] 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 59ff6f4dff7c6..16ee20f076c74 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18122,11 +18122,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 47019876b7233..3db0409d752ac 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6726,7 +6726,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 f942385d3bd3f7806983008f8f394ee0bc47fabe Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 12 May 2026 19:22:43 +0200 Subject: [PATCH 03/21] 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 e4042876b4bbb..48c1a9d8da75a 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,8 +3904,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 f86f0157b2b1f..da80db91e840f 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1032,6 +1032,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 79583534b9bbd..4ec1b1eb2bf02 100644 --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -1680,6 +1680,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 8c20c95c3bcaa..a724540d85eb4 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15699,8 +15699,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 d76365e59fb35..5e4bbd2098fd0 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2591,10 +2591,8 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && EStatus.DiagEmitted) { - 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 16ee20f076c74..c8d6834046e6f 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18124,9 +18124,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 3db0409d752ac..31f243f74e0c2 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6728,9 +6728,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 a2bc25281e83f6126eb5e2931becda1d4a0a8c80 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:00:59 +0200 Subject: [PATCH 04/21] 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 da80db91e840f..248cbf3a64870 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1032,9 +1032,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 bdc352f993f311ab5e092d877ee998d41f4848c7 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 17:07:02 +0200 Subject: [PATCH 05/21] 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 a724540d85eb4..33c1d993d081c 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15701,8 +15701,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 811ea10d05b84a892883ab3ed0ba58d583cc9e01 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 14 May 2026 20:02:28 +0200 Subject: [PATCH 06/21] 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 248cbf3a64870..3d15e80130ca1 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1033,10 +1033,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 33c1d993d081c..9c7e4ce496ac0 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15701,14 +15701,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 03118a87317e6132b133f56caf475c6a00856d09 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 18 May 2026 13:50:18 +0200 Subject: [PATCH 07/21] 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 3d15e80130ca1..54c953d8c59f2 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 9c7e4ce496ac0..050e8798a3f57 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15704,23 +15704,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 574dd8b04e779..37b01b8251842 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8731,7 +8731,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); } @@ -20044,7 +20044,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 42792f0b3667d2e38ff32795ad67214cf5668939 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 19 May 2026 14:49:52 +0200 Subject: [PATCH 08/21] 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 48c1a9d8da75a..ffcb868291c1c 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,7 +3904,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 050e8798a3f57..6b8a44c23bb5a 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15700,8 +15700,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 5e4bbd2098fd0..caf03ea6fc5df 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2591,7 +2591,7 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && EStatus.DiagEmitted) { - 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 c8d6834046e6f..d49f0e783e0e4 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18125,7 +18125,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 31f243f74e0c2..90c8df7434409 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6729,7 +6729,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 991d32b2947441744f222922e7ec6601ebb6ac94 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 11:46:54 +0200 Subject: [PATCH 09/21] 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 ffcb868291c1c..602dc766c3e11 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,8 +3904,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 6b8a44c23bb5a..ad14d398d531f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15699,7 +15699,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 caf03ea6fc5df..55a04b4984421 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2591,7 +2591,7 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && EStatus.DiagEmitted) { - 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 d49f0e783e0e4..d9868659e049e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18125,7 +18125,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 90c8df7434409..bd8000f92350d 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6729,7 +6729,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 d1381c643917fe43b0c8ff635d2ff83f78660065 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 25 May 2026 16:03:26 +0200 Subject: [PATCH 10/21] 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 55a04b4984421..bc1788f20efa7 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2587,7 +2587,7 @@ 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)) && EStatus.DiagEmitted) { diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 37b01b8251842..62c25e7417cec 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8731,7 +8731,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 b28d9ba22d8578ac0843fb792840dee2d536d38d Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 15:20:17 +0200 Subject: [PATCH 11/21] 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 | 1 + clang/lib/Sema/SemaExpr.cpp | 4 ++-- clang/lib/Sema/SemaOverload.cpp | 4 ++-- clang/test/SemaCXX/microsoft-constexpr2.cpp | 2 ++ 8 files changed, 17 insertions(+), 11 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 602dc766c3e11..306644f3196aa 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,8 +3904,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 53107e2794174..f306429aa690d 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -622,6 +622,10 @@ class Expr : public ValueStmt { /// Whether any diagnostic has been emitted. This is set regardless of /// whether @ref #Diag is set or not. bool DiagEmitted = 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 diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index ad14d398d531f..129c3bd3478f7 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15700,8 +15700,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 bc1788f20efa7..c17aa267242a6 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2587,12 +2587,11 @@ 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)) && EStatus.DiagEmitted) { - 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 62c25e7417cec..6680157361ec7 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -20055,6 +20055,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. diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index d9868659e049e..fbf9c1f0f942d 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18124,8 +18124,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 bd8000f92350d..1eb6e5963dcb5 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6728,8 +6728,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 3956fdb4240a161ab1bf2c35965f7bfe6109a95b Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 26 May 2026 21:35:51 +0200 Subject: [PATCH 12/21] 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 0360a29571f7b77d11d15d7124dbf643d9dac7e4 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Mon, 1 Jun 2026 14:41:39 +0200 Subject: [PATCH 13/21] 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 e62b77272046c..84ec99b99308f 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); @@ -44,6 +61,7 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { EvalStatus.DiagEmitted = true; + 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 df3afdf8cbc24..4bfff6b939f6a 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -92,6 +92,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 9df361efc5b48e82dd979cf14e48707a01615690 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Wed, 3 Jun 2026 10:00:08 +0200 Subject: [PATCH 14/21] 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 129c3bd3478f7..13c0058a0fddf 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15699,9 +15699,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) @@ -15709,6 +15716,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 84ec99b99308f..9c2f7956992ba 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 db4b0ac005027ecd4091c4912ef3429265f3fba1 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 5 Jun 2026 17:34:41 +0200 Subject: [PATCH 15/21] 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 | 8 +++++ .../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 | 14 ++++++-- 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, 49 insertions(+), 64 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index 306644f3196aa..7ed6509c3c16c 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -3904,9 +3904,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 f306429aa690d..71be307d672de 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -626,6 +626,14 @@ class Expr : public ValueStmt { /// 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 54c953d8c59f2..164ecf36e2bff 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 " @@ -1038,8 +1039,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 13c0058a0fddf..2228811546c0f 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -15698,23 +15698,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 9c2f7956992ba..d038c730c8d38 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, @@ -74,7 +57,6 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { EvalStatus.DiagEmitted = true; - 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()) { @@ -118,7 +100,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 4bfff6b939f6a..5efef3f707042 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -92,7 +92,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 6680157361ec7..3c266176c8afa 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2435,6 +2435,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, @@ -2547,6 +2555,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; } @@ -20055,7 +20066,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. @@ -22037,7 +22048,6 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, const ASTContext &Ctx, // destruction. return false; } - return true; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index fbf9c1f0f942d..5ad4b0773f39e 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18124,8 +18124,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 1eb6e5963dcb5..e3eb6969285fd 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6708,6 +6708,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"); @@ -6728,9 +6729,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 5190095d85086b111e53ab7c59b26d082a73c59f Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 5 Jun 2026 17:58:10 +0200 Subject: [PATCH 16/21] 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, >From 9d8c951ab0de9f9c8794fccc37191c6fcbcb0d4f Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 5 Jun 2026 18:13:16 +0200 Subject: [PATCH 17/21] Remove unused code #2 --- clang/lib/AST/Decl.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index c17aa267242a6..4eaef0d87f3e5 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2590,9 +2590,8 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, if (IsConstantInitialization && (Ctx.getLangOpts().CPlusPlus || (isConstexpr() && Ctx.getLangOpts().C23)) && - EStatus.DiagEmitted) { + EStatus.DiagEmitted) Result = false; - } // 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 >From 5216aeb94f5e0183198634b20c580b5aeac927ff Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 30 Jun 2026 16:37:09 +0200 Subject: [PATCH 18/21] Rebase and fix issues --- clang/include/clang/AST/Expr.h | 4 ++-- clang/lib/AST/ByteCode/State.cpp | 6 +++++- clang/test/SemaCXX/microsoft-constexpr2.cpp | 1 - 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 71be307d672de..bfef86cebada4 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -622,11 +622,11 @@ class Expr : public ValueStmt { /// Whether any diagnostic has been emitted. This is set regardless of /// whether @ref #Diag is set or not. bool DiagEmitted = 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; diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index d038c730c8d38..21e2bb6018af4 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -56,6 +56,10 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { + if (shouldRelaxDiag(DiagId)) { + setActiveDiagnostic(false); + return OptionalDiagnostic(); + } EvalStatus.DiagEmitted = true; // Don't override a previous diagnostic. Don't bother collecting // diagnostics if we're evaluating for overflow. @@ -100,7 +104,7 @@ PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) { OptionalDiagnostic State::diag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes, bool IsCCEDiag) { - if (EvalStatus.Diag && !shouldRelaxDiag(DiagId)) { + if (EvalStatus.Diag) { if (hasPriorDiagnostic()) { return OptionalDiagnostic(); } diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 8a1aee4dc8bcb..6fd70135361f7 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -15,7 +15,6 @@ 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 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}} >From af7cc4783ec9e77650aa8d087430f09411730f8e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Tue, 7 Jul 2026 18:31:17 +0200 Subject: [PATCH 19/21] Code cleanup 1. Removed unused variable HasLValue 2. Removed IsConvertedExpr and related check --- clang/include/clang/AST/Expr.h | 8 -------- clang/include/clang/Basic/DiagnosticASTKinds.td | 2 +- clang/lib/AST/ExprConstant.cpp | 3 +-- clang/lib/Sema/SemaOverload.cpp | 1 - clang/test/SemaCXX/microsoft-constexpr2.cpp | 13 +++++++++---- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index bfef86cebada4..5c176392dbc94 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -623,18 +623,10 @@ class Expr : public ValueStmt { /// whether @ref #Diag is set or not. bool DiagEmitted = 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 /// expression). diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 164ecf36e2bff..311e78606d9af 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -20,7 +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_has_lvalue : Note<"converting the address of an object to an integer is not allowed">; 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/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 3c266176c8afa..2c6df238e8a57 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2438,8 +2438,7 @@ static bool CheckLiteralType(EvalInfo &Info, const Expr *E, static void CheckMicrosoftRelaxations(EvalInfo &Info, const SourceLocation &Loc) { auto *Diag = Info.EvalStatus.Diag; - if (Diag && Diag->empty() && Info.EvalStatus.SeenCastOrNull && - !Info.EvalStatus.IsConvertedExpr) + if (Diag && Diag->empty() && Info.EvalStatus.SeenCastOrNull) Info.report(Loc, diag::warn_relaxed_constant_fold); } diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index e3eb6969285fd..d075682c6fc51 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6708,7 +6708,6 @@ 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"); diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 6fd70135361f7..6405de7549f69 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -14,19 +14,24 @@ struct S { template<bool B> struct TplBool {}; +template<int V> +struct TplInt {}; + 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}} +TplInt<FIELD_OFFSET(S, y)> ti; // expected-warning {{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 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 {{constant expression contains l-value}} + // expected-note@-1 {{converting the address of an object to an integer is not allowed}} 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 {{constant expression contains l-value}} + (8 + reinterpret_cast<LONG_PTR>(&ob)); // expected-note {{converting the address of an object to an integer is not allowed}} constexpr LONG_PTR b6 = -reinterpret_cast<LONG_PTR>(&ob); // expected-error {{constexpr variable 'b6' must be initialized by a constant expression}} - // expected-note@-1 {{constant expression contains l-value}} + // expected-note@-1 {{converting the address of an object to an integer is not allowed}} 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 {{constant expression contains l-value}} + // expected-note@-1 {{converting the address of an object to an integer is not allowed}} 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}} >From 38f8f4e03a948713e1676cf3b54cc99b069c4dd6 Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Fri, 10 Jul 2026 19:23:16 +0200 Subject: [PATCH 20/21] Change how we emit warning message Instead of popping warning in constant evaluator we now save the location, so the caller can print the diagnostics --- clang/include/clang/AST/Expr.h | 8 ++++---- clang/lib/AST/ByteCode/State.cpp | 6 +++--- clang/lib/AST/ByteCode/State.h | 2 +- clang/lib/AST/Decl.cpp | 9 +++++++-- clang/lib/AST/ExprConstant.cpp | 10 ---------- clang/lib/Sema/SemaExpr.cpp | 8 ++++++-- clang/lib/Sema/SemaOverload.cpp | 5 ++++- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 5c176392dbc94..76a19d8283114 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -623,10 +623,6 @@ class Expr : public ValueStmt { /// whether @ref #Diag is set or not. bool DiagEmitted = false; - /// Whether we've seen a ptr to int cast or null subobject while evaluating - /// constant expression in MS compatibility mode. - bool SeenCastOrNull = 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). @@ -643,6 +639,10 @@ class Expr : public ValueStmt { /// to a string representation). SmallVectorImpl<PartialDiagnosticAt> *Diag = nullptr; + /// Location where we spot ptr to int cast or null subobject while + /// evaluating constant expression in MS compatibility mode. + SourceLocation CastOrNull; + EvalStatus() = default; /// Return true if the evaluated expression has diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 21e2bb6018af4..9cb2c625a432d 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -18,13 +18,13 @@ using namespace clang::interp; State::~State() {} -bool State::shouldRelaxDiag(diag::kind DiagId) { +bool State::shouldRelaxDiag(const SourceLocation &Loc, diag::kind DiagId) { if (!Ctx.getLangOpts().MSVCCompat) return false; switch (DiagId) { case diag::note_constexpr_invalid_cast_ptrtoint: case diag::note_constexpr_null_subobject: - EvalStatus.SeenCastOrNull = true; + EvalStatus.CastOrNull = Loc; return true; default: return false; @@ -56,7 +56,7 @@ OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { - if (shouldRelaxDiag(DiagId)) { + if (shouldRelaxDiag(Loc, DiagId)) { setActiveDiagnostic(false); return OptionalDiagnostic(); } diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 5efef3f707042..96b73a48662f5 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -92,7 +92,7 @@ class State { ASTContext &getASTContext() const { return Ctx; } const LangOptions &getLangOpts() const { return Ctx.getLangOpts(); } - bool shouldRelaxDiag(diag::kind DiagId); + bool shouldRelaxDiag(const SourceLocation &Loc, diag::kind DiagId); /// Note that we have had a side-effect, and determine whether we should /// keep evaluating. diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 4eaef0d87f3e5..64b3f9cd65912 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2598,8 +2598,13 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, // failed. if (!Result) Eval->Evaluated = APValue(); - else if (Eval->Evaluated.needsCleanup()) - Ctx.addDestruction(&Eval->Evaluated); + else { + if (EStatus.CastOrNull.isValid()) + getASTContext().getDiagnostics().Report(EStatus.CastOrNull, + diag::warn_relaxed_constant_fold); + if (Eval->Evaluated.needsCleanup()) + Ctx.addDestruction(&Eval->Evaluated); + } Eval->IsEvaluating = false; Eval->WasEvaluated = true; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 2c6df238e8a57..60cd668bb63e1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2435,13 +2435,6 @@ 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.report(Loc, diag::warn_relaxed_constant_fold); -} - static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, EvalInfo &Info, SourceLocation DiagLoc, QualType Type, const APValue &Value, @@ -2554,9 +2547,6 @@ 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; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 5ad4b0773f39e..5601b5eb9caaa 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18124,8 +18124,12 @@ 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.SeenCastOrNull) - Folded = false; + if (EvalResult.CastOrNull.isValid()) { + if (isSFINAEContext()) + Folded = false; + else + Diag(EvalResult.CastOrNull, diag::warn_relaxed_constant_fold); + } // 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 d075682c6fc51..9a07eebf72887 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -12,6 +12,7 @@ #include "CheckExprLifetime.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDiagnostic.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" @@ -6728,8 +6729,10 @@ 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 - bool CantFold = isSFINAEContext() && Eval.SeenCastOrNull; + bool CantFold = isSFINAEContext() && Eval.CastOrNull.isValid(); if (Notes.empty() && !CantFold) { + if (Eval.CastOrNull.isValid()) + Diag(Eval.CastOrNull, diag::warn_relaxed_constant_fold); // It's a constant expression. Expr *E = Result.get(); if (const auto *CE = dyn_cast<ConstantExpr>(E)) { >From fadae52aa8c8be31a03407eda85add3db383e64e Mon Sep 17 00:00:00 2001 From: Evgeny Leviant <[email protected]> Date: Thu, 16 Jul 2026 12:48:48 +0200 Subject: [PATCH 21/21] Change how we emit warning We're now emitting warning at call sites, where we start evaluating constant expressions. Additionally new warning message is introduced for taking an address of null pointer. --- clang/include/clang/AST/Expr.h | 2 +- clang/include/clang/Basic/DiagnosticASTKinds.td | 6 +++++- clang/lib/AST/ByteCode/State.cpp | 11 ++++++++++- clang/lib/AST/ByteCode/State.h | 2 ++ clang/lib/AST/Decl.cpp | 9 ++++++--- clang/lib/AST/ExprConstant.cpp | 7 +++---- clang/lib/Sema/SemaExpr.cpp | 12 ++++++++---- clang/lib/Sema/SemaOverload.cpp | 8 +++++--- clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++++ 9 files changed, 44 insertions(+), 17 deletions(-) diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 76a19d8283114..a011435e2f59d 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -641,7 +641,7 @@ class Expr : public ValueStmt { /// Location where we spot ptr to int cast or null subobject while /// evaluating constant expression in MS compatibility mode. - SourceLocation CastOrNull; + SmallVectorImpl<PartialDiagnosticAt> *ExtendedDiag = nullptr; EvalStatus() = default; diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 311e78606d9af..32dfe5dd2e7e5 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -1037,11 +1037,15 @@ 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 : Extension< +def warn_relaxed_constant_fold_cast : Extension< "folding constant expression involving " "cast that performs the conversions of a reinterpret_cast " "is a Microsoft extension">, InGroup<MicrosoftRelaxedConstantFold>; +def warn_relaxed_constant_fold_null : Extension< + "folding constant expression that takes field address of null pointer " + "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/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp index 9cb2c625a432d..0c5f1c4501c21 100644 --- a/clang/lib/AST/ByteCode/State.cpp +++ b/clang/lib/AST/ByteCode/State.cpp @@ -23,8 +23,10 @@ bool State::shouldRelaxDiag(const SourceLocation &Loc, diag::kind DiagId) { return false; switch (DiagId) { case diag::note_constexpr_invalid_cast_ptrtoint: + addExtendedDiag(Loc, diag::warn_relaxed_constant_fold_cast); + return true; case diag::note_constexpr_null_subobject: - EvalStatus.CastOrNull = Loc; + addExtendedDiag(Loc, diag::warn_relaxed_constant_fold_null); return true; default: return false; @@ -102,6 +104,13 @@ PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) { return EvalStatus.Diag->back().second; } +void State::addExtendedDiag(SourceLocation Loc, diag::kind DiagId) { + if (!EvalStatus.ExtendedDiag) + return; + PartialDiagnostic PD(DiagId, Ctx.getDiagAllocator()); + EvalStatus.ExtendedDiag->push_back(std::make_pair(Loc, PD)); +} + OptionalDiagnostic State::diag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes, bool IsCCEDiag) { if (EvalStatus.Diag) { diff --git a/clang/lib/AST/ByteCode/State.h b/clang/lib/AST/ByteCode/State.h index 96b73a48662f5..9ebd1e3845542 100644 --- a/clang/lib/AST/ByteCode/State.h +++ b/clang/lib/AST/ByteCode/State.h @@ -205,6 +205,8 @@ class State { PartialDiagnostic &addDiag(SourceLocation Loc, diag::kind DiagId); + void addExtendedDiag(SourceLocation Loc, diag::kind DiagId); + OptionalDiagnostic diag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes, bool IsCCEDiag); diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 64b3f9cd65912..d3cbabfadd403 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2576,9 +2576,11 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, Eval->IsEvaluating = true; + SmallVector<PartialDiagnosticAt> MSWarning; ASTContext &Ctx = getASTContext(); Expr::EvalResult EStatus; EStatus.Diag = Notes; + EStatus.ExtendedDiag = &MSWarning; bool Result = Init->EvaluateAsInitializer(Ctx, this, EStatus, IsConstantInitialization); Eval->Evaluated = std::move(EStatus.Val); @@ -2599,9 +2601,10 @@ VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> *Notes, if (!Result) Eval->Evaluated = APValue(); else { - if (EStatus.CastOrNull.isValid()) - getASTContext().getDiagnostics().Report(EStatus.CastOrNull, - diag::warn_relaxed_constant_fold); + if (!MSWarning.empty()) + for (auto &Info : MSWarning) + getASTContext().getDiagnostics().Report(Info.first, + Info.second.getDiagID()); if (Eval->Evaluated.needsCleanup()) Ctx.addDestruction(&Eval->Evaluated); } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 60cd668bb63e1..b581a941b3bb2 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8731,10 +8731,9 @@ class ExprEvaluatorBase } bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) { - bool IsPtrToInt = E->getCastKind() == CK_PointerToIntegral; - CCEDiag(E, IsPtrToInt ? diag::note_constexpr_invalid_cast_ptrtoint - : diag::note_constexpr_invalid_cast) - << diag::ConstexprInvalidCastKind::Reinterpret; + if (E->getCastKind() != CK_PointerToIntegral) + CCEDiag(E, diag::note_constexpr_invalid_cast) + << diag::ConstexprInvalidCastKind::Reinterpret; return static_cast<Derived*>(this)->VisitCastExpr(E); } bool VisitCXXDynamicCastExpr(const CXXDynamicCastExpr *E) { diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 5601b5eb9caaa..f0c5dd344f734 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -18110,7 +18110,9 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, Expr::EvalResult EvalResult; SmallVector<PartialDiagnosticAt, 8> Notes; + SmallVector<PartialDiagnosticAt> MSWarning; EvalResult.Diag = &Notes; + EvalResult.ExtendedDiag = &MSWarning; // Try to evaluate the expression, and produce diagnostics explaining why it's // not a constant expression as a side-effect. @@ -18124,11 +18126,13 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result, // For -fms-compatibility mode we relax some requirements // for constant folding in non-SFINAE contexts - if (EvalResult.CastOrNull.isValid()) { - if (isSFINAEContext()) + if (!MSWarning.empty()) { + if (isSFINAEContext()) { Folded = false; - else - Diag(EvalResult.CastOrNull, diag::warn_relaxed_constant_fold); + } else { + for (auto &Info : MSWarning) + Diag(Info.first, Info.second); + } } // In C++11, we can rely on diagnostics being produced for any expression diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 9a07eebf72887..ad24510b4f1d9 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -6707,8 +6707,10 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value, ExprResult Result = E; // Check the expression is a constant expression. SmallVector<PartialDiagnosticAt, 8> Notes; + SmallVector<PartialDiagnosticAt> MSWarning; Expr::EvalResult Eval; Eval.Diag = &Notes; + Eval.ExtendedDiag = &MSWarning; assert(CCE != CCEKind::TempArgStrict && "unnexpected CCE Kind"); @@ -6729,10 +6731,10 @@ 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 - bool CantFold = isSFINAEContext() && Eval.CastOrNull.isValid(); + bool CantFold = isSFINAEContext() && !MSWarning.empty(); if (Notes.empty() && !CantFold) { - if (Eval.CastOrNull.isValid()) - Diag(Eval.CastOrNull, diag::warn_relaxed_constant_fold); + for (auto &Info : MSWarning) + Diag(Info.first, Info.second); // It's a constant expression. Expr *E = Result.get(); if (const auto *CE = dyn_cast<ConstantExpr>(E)) { diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp index 6405de7549f69..2430f082909f1 100644 --- a/clang/test/SemaCXX/microsoft-constexpr2.cpp +++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp @@ -19,9 +19,13 @@ struct TplInt {}; 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}} + // expected-warning@-2 {{folding constant expression that takes field address of null pointer is a Microsoft extension}} TplInt<FIELD_OFFSET(S, y)> ti; // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}} + // expected-warning@-1 {{folding constant expression that takes field address of null pointer 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}} + // expected-warning@-1 {{folding constant expression that takes field address of null pointer 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}} + // expected-warning@-1 {{folding constant expression that takes field address of null pointer 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 {{converting the address of an object to an integer is not allowed}} constexpr int* b4 = reinterpret_cast<int*>(&ob); // expected-error {{constexpr variable 'b4' must be initialized by a constant expression}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
