[clang] [clang] Allow C-style casts in constexpr in MS compatible mode (PR #197005)

via cfe-commits cfe-commits at lists.llvm.org
Fri Aug 14 10:59:16 PDT 2026


https://github.com/eleviant updated https://github.com/llvm/llvm-project/pull/197005

>From e0a99a15383edecdec9ab4b1ab586435e6ede2f6 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 11 May 2026 19:39:23 +0200
Subject: [PATCH 01/24] [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               |   5 +
 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, 160 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 56b51566f58f5..bcde882a4091c 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,6 +3942,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 35a2a0b131a3f..e3b490645b6e9 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16011,4 +16011,9 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
   assert(res && "Failed to insert?");
 
   return itr->second;
+
+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 152c621bc1ef4..11bb276ee1f6a 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2592,8 +2592,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 f25829ae676dc..727e7e0621d33 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18115,6 +18115,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 403bd784ddc7b..95c9ae6067736 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 e7c89943eee9407ce453afb7f254ce8a9efd7821 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 12 May 2026 16:52:07 +0200
Subject: [PATCH 02/24] 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 727e7e0621d33..e7875854d4f5a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18112,11 +18112,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 95c9ae6067736..3bd3d25419be5 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 at -1 {{static assertion failed due to requirement 'sizeof (probe<int>(0)) == sizeof(char)'}}
+// expected-note at -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 at -1 {{static assertion failed due to requirement 'sizeof (probe<S>(0)) == sizeof(char)'}}
+// expected-note at -2 {{expression evaluates to '4 == 1'}}

>From b2df0235d33593dca7a14b098575855ca992329c Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 12 May 2026 19:22:43 +0200
Subject: [PATCH 03/24] 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 bcde882a4091c..34137fab5d593 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,8 +3942,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 c4582a3a9d641..c3e8d296bff79 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1033,6 +1033,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 ba820778b80c0..d44f69a815784 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1688,6 +1688,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 e3b490645b6e9..e687ced44398d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16012,8 +16012,13 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
 
   return itr->second;
 
-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 11bb276ee1f6a..9ef012013edfd 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2593,10 +2593,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 e7875854d4f5a..cbf8d99156c15 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18114,9 +18114,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 3bd3d25419be5..095ea9b6a14f8 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 d9ac0b5209356f37fed20b4250c0a22b87dfd567 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 17:00:59 +0200
Subject: [PATCH 04/24] 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 c3e8d296bff79..24f12ca6b91b6 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1033,9 +1033,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 c79a79e1d771a063da616f24c752ee927c411577 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 17:07:02 +0200
Subject: [PATCH 05/24] 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 e687ced44398d..a59dce7140cdb 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16014,8 +16014,11 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
 
 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 3fc9edea920b3f5cdad57e5e152058c84b6987a8 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 20:02:28 +0200
Subject: [PATCH 06/24] 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 24f12ca6b91b6..81ef95c274711 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1034,10 +1034,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 a59dce7140cdb..7066b049d3da3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16014,14 +16014,26 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
 
 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 fcd00daa44371a8228f30f607b0a5ee5dfe6abd7 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 18 May 2026 13:50:18 +0200
Subject: [PATCH 07/24] 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 81ef95c274711..253d41882097e 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 7066b049d3da3..d09bb82b7dae5 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16017,23 +16017,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 480d5119a5363..06b4d29296f8d 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8742,7 +8742,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);
   }
@@ -20053,7 +20053,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 ee1cf68d8cefa54b5dbd8d63ebf6b885b2936936 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 19 May 2026 14:49:52 +0200
Subject: [PATCH 08/24] 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 34137fab5d593..8fcde89b98db3 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,7 +3942,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 d09bb82b7dae5..8829dc04e7073 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16013,8 +16013,8 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
   return itr->second;
 
 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 9ef012013edfd..b870a10120fa6 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2593,7 +2593,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 cbf8d99156c15..b71c5bfb3b186 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18115,7 +18115,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 095ea9b6a14f8..636c392a48c56 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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}

>From cd667bb90b2f7577e420906ea3e8104e5ed5a788 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 25 May 2026 11:46:54 +0200
Subject: [PATCH 09/24] 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 8fcde89b98db3..7cfac92ff8e8d 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,8 +3942,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 8829dc04e7073..af6a54a154297 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16012,7 +16012,7 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
 
   return itr->second;
 
-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 b870a10120fa6..0889adabf9af5 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2593,7 +2593,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 b71c5bfb3b186..2fb5e523c83fb 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18115,7 +18115,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 636c392a48c56..1b1a570e0c6a0 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 1876a5da27792cc2d6a97bb1c432737674704cf4 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 25 May 2026 16:03:26 +0200
Subject: [PATCH 10/24] 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 0889adabf9af5..888ed29c5c237 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2589,7 +2589,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 06b4d29296f8d..4c5bb19a0102c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8742,7 +8742,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 at -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 at -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 at -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 at -1 {{reinterpret_cast is not allowed in a constant expression}}

>From 58798f486aac9124802e01aa408ba0d5874b2e32 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 26 May 2026 15:20:17 +0200
Subject: [PATCH 11/24] 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 7cfac92ff8e8d..e7517e1e07a1f 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,8 +3942,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 f95f87cc4e8e0..bdd47518d7c52 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 af6a54a154297..d80cc28c53b0a 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16013,8 +16013,8 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
   return itr->second;
 
 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 888ed29c5c237..bbd74ab0a2dea 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2589,12 +2589,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 4c5bb19a0102c..222a31bac9349 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -20064,6 +20064,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 2fb5e523c83fb..78540902e3c9f 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18114,8 +18114,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 1b1a570e0c6a0..c38de97dabf21 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 at -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 at -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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}

>From 48adee03861f2da58c0be7edfbe11a2361ded2ad Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 26 May 2026 21:35:51 +0200
Subject: [PATCH 12/24] 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 at -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 at -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 at -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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}

>From 6fc69ce2057bae916a577f34ca9f9088a51d061e Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 1 Jun 2026 14:41:39 +0200
Subject: [PATCH 13/24] 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 38384bae23f57..ac4234ddfe638 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 df91132c48472..9309988267f66 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 at -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 at -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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}

>From 1fce476a251bf19b3e9264ab613c375e96d5dbc9 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Wed, 3 Jun 2026 10:00:08 +0200
Subject: [PATCH 14/24] 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 d80cc28c53b0a..5e0b725219b0e 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16012,9 +16012,16 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
 
   return itr->second;
 
+// 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)
@@ -16022,6 +16029,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 ac4234ddfe638..850e748f646ad 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 f598a704882d7e6504686a40537539f5122c0158 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 5 Jun 2026 17:34:41 +0200
Subject: [PATCH 15/24] 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                  | 19 -----------
 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(+), 63 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index e7517e1e07a1f..56b51566f58f5 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3942,9 +3942,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 bdd47518d7c52..2cc9bb0fd3d5a 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 253d41882097e..f75a7548c8c58 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 "
@@ -1039,8 +1040,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 5e0b725219b0e..35a2a0b131a3f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -16011,23 +16011,4 @@ ASTContext::getPaddingIntervals(QualType Ty) const {
   assert(res && "Failed to insert?");
 
   return itr->second;
-
-// 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 850e748f646ad..6854e18a217f4 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()) {
@@ -124,7 +106,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 9309988267f66..ab795495a5c46 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 222a31bac9349..954c86ebea011 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2439,6 +2439,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,
@@ -2551,6 +2559,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;
 }
@@ -20064,7 +20075,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.
@@ -22047,7 +22058,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 78540902e3c9f..00f8795ad11f8 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18114,8 +18114,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 c38de97dabf21..677d3931c03f3 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 at -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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}
+				       // expected-note at -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 at -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 at -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 at -1 {{reinterpret_cast is not allowed in a constant expression}}
+							  // expected-note at -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 at -1 {{cast that performs the conversions of a reinterpret_cast is not allowed in a constant expression}}
+		  					          // expected-note at -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 at -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 at -1 {{division by zero}}

>From cbebfbd8f1ef6d3789e9a48ae70e3d275e5c789d Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 5 Jun 2026 17:58:10 +0200
Subject: [PATCH 16/24] 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 a781ff8c26da4b2cc9c68473b917f8473554ae5c Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 5 Jun 2026 18:13:16 +0200
Subject: [PATCH 17/24] 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 bbd74ab0a2dea..152c621bc1ef4 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2592,9 +2592,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 7f737262a67ff747085d0026d38e4b26fefe88b7 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 30 Jun 2026 16:37:09 +0200
Subject: [PATCH 18/24] 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 2cc9bb0fd3d5a..a17b2b21d4640 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 6854e18a217f4..3a6f5b6d8be43 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.
@@ -106,7 +110,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 at -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 64b54018804c9164d016c499ebda7933938e75c0 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 7 Jul 2026 18:31:17 +0200
Subject: [PATCH 19/24] 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 a17b2b21d4640..5c8fe36b4f3df 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 f75a7548c8c58..36f6771c3b423 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 954c86ebea011..ff35ecfae213e 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2442,8 +2442,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 677d3931c03f3..ac211867b7360 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 at -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 at -1 {{constant expression contains l-value}}
+				       // expected-note at -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 at -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 at -1 {{constant expression contains l-value}}
+							  // expected-note at -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 at -1 {{constant expression contains l-value}}
+                                                                  // expected-note at -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 at -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 776263f7771cb415cd4f570b2731b62292ed6c86 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 10 Jul 2026 19:23:16 +0200
Subject: [PATCH 20/24] 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 5c8fe36b4f3df..dc2d8b91bbc1b 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 3a6f5b6d8be43..e690b92c6bd7c 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 ab795495a5c46..58d0bbc6ce31d 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 152c621bc1ef4..ae7f9d9111e24 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2600,8 +2600,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 ff35ecfae213e..8ee28a5bcd7cb 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2439,13 +2439,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,
@@ -2558,9 +2551,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 00f8795ad11f8..3e9cc9fcfd02c 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18114,8 +18114,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 ac211867b7360..167f31cc937bb 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 8d56b436d0af5df347eca503dcef4382bc94b39e Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 16 Jul 2026 12:48:48 +0200
Subject: [PATCH 21/24] 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 dc2d8b91bbc1b..53931cb588d10 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 36f6771c3b423..3c39ccf51ab67 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1038,11 +1038,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 e690b92c6bd7c..14458c4ebc4fe 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;
@@ -108,6 +110,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 58d0bbc6ce31d..97af0bf2b6866 100644
--- a/clang/lib/AST/ByteCode/State.h
+++ b/clang/lib/AST/ByteCode/State.h
@@ -206,6 +206,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 ae7f9d9111e24..55f34e27ae535 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2578,9 +2578,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);
@@ -2601,9 +2603,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 8ee28a5bcd7cb..6d4a7c19e9b1c 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8742,10 +8742,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 3e9cc9fcfd02c..0908841dca8bf 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18100,7 +18100,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.
@@ -18114,11 +18116,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 167f31cc937bb..c98754679d10b 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 at -1 {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}}
+				// expected-warning at -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 at -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 at -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 at -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 at -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}}

>From 53a823342a0c9e36dd8997863b0a9049feea467f Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 24 Jul 2026 17:52:59 +0200
Subject: [PATCH 22/24] Emit warnings only when EvalStatus::ExtendedDiag is set

---
 clang/include/clang/AST/ASTContext.h | 2 ++
 clang/lib/AST/ByteCode/State.cpp     | 4 +---
 clang/lib/AST/ExprConstant.cpp       | 1 +
 clang/lib/Sema/SemaOverload.cpp      | 4 ++++
 4 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 56b51566f58f5..3c3edb5a7b026 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -46,6 +46,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/TinyPtrVector.h"
+#include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/TypeSize.h"
 #include <optional>
 
@@ -827,6 +828,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
   mutable DeclarationNameTable DeclarationNames;
   IntrusiveRefCntPtr<ExternalASTSource> ExternalSource;
   ASTMutationListener *Listener = nullptr;
+  SmallVector<PartialDiagnosticAt> *MSConstExprDiag = nullptr;
 
   /// Returns the clang bytecode interpreter context.
   interp::Context &getInterpContext() const;
diff --git a/clang/lib/AST/ByteCode/State.cpp b/clang/lib/AST/ByteCode/State.cpp
index 14458c4ebc4fe..d1087f0da92d5 100644
--- a/clang/lib/AST/ByteCode/State.cpp
+++ b/clang/lib/AST/ByteCode/State.cpp
@@ -19,7 +19,7 @@ using namespace clang::interp;
 State::~State() {}
 
 bool State::shouldRelaxDiag(const SourceLocation &Loc, diag::kind DiagId) {
-  if (!Ctx.getLangOpts().MSVCCompat)
+  if (!Ctx.getLangOpts().MSVCCompat || !EvalStatus.ExtendedDiag)
     return false;
   switch (DiagId) {
   case diag::note_constexpr_invalid_cast_ptrtoint:
@@ -111,8 +111,6 @@ PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) {
 }
 
 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));
 }
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 6d4a7c19e9b1c..eb09e12dd7b45 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -22818,6 +22818,7 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result) const {
   // Build evaluation settings.
   Expr::EvalStatus Status;
   EvalInfo Info(Ctx, Status, EvaluationMode::ConstantExpression);
+  Status.ExtendedDiag = Ctx.MSConstExprDiag;
 
   bool IsConstExpr =
       ::EvaluateAsRValue(Info, this, Result ? *Result : Scratch) &&
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index c98754679d10b..13a2aa722de0f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6675,6 +6675,10 @@ ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T,
                                                   CCEKind CCE,
                                                   NamedDecl *Dest) {
   APValue PreNarrowingValue;
+  SmallVector<PartialDiagnosticAt> MSWarning;
+  ASTContext &Ctx = getASTContext();
+  llvm::SaveAndRestore<SmallVector<PartialDiagnosticAt> *> SAR(
+      Ctx.MSConstExprDiag, Ctx.getLangOpts().MSVCCompat ? &MSWarning : nullptr);
   return ::BuildConvertedConstantExpression(*this, From, T, CCE, Dest,
                                             PreNarrowingValue);
 }

>From d94aa3bf0ce35aeff60fb3eece1fea657fe9679e Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 27 Jul 2026 21:36:02 +0200
Subject: [PATCH 23/24] Explicitly pass down flag to allow relaxed constexpr
 evaluation

---
 clang/include/clang/AST/ASTContext.h |  1 -
 clang/include/clang/AST/Expr.h       |  7 ++++---
 clang/include/clang/Sema/Overload.h  |  9 +++++----
 clang/lib/AST/ExprConstant.cpp       | 21 +++++++++++++--------
 clang/lib/Sema/SemaOverload.cpp      | 21 ++++++++++++---------
 5 files changed, 34 insertions(+), 25 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 3c3edb5a7b026..80ac5fbdae047 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -828,7 +828,6 @@ class ASTContext : public RefCountedBase<ASTContext> {
   mutable DeclarationNameTable DeclarationNames;
   IntrusiveRefCntPtr<ExternalASTSource> ExternalSource;
   ASTMutationListener *Listener = nullptr;
-  SmallVector<PartialDiagnosticAt> *MSConstExprDiag = nullptr;
 
   /// Returns the clang bytecode interpreter context.
   interp::Context &getInterpContext() const;
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 53931cb588d10..112af378258fc 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -562,7 +562,8 @@ class Expr : public ValueStmt {
   /// Note: This does not perform the implicit conversions required by C++11
   /// [expr.const]p5.
   std::optional<llvm::APSInt>
-  getIntegerConstantExpr(const ASTContext &Ctx) const;
+  getIntegerConstantExpr(const ASTContext &Ctx,
+                         bool AllowRelaxedEval = false) const;
   bool isIntegerConstantExpr(const ASTContext &Ctx) const;
 
   /// isCXX98IntegralConstantExpr - Return true if this expression is an
@@ -574,8 +575,8 @@ class Expr : public ValueStmt {
   ///
   /// Note: This does not perform the implicit conversions required by C++11
   /// [expr.const]p5.
-  bool isCXX11ConstantExpr(const ASTContext &Ctx,
-                           APValue *Result = nullptr) const;
+  bool isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result = nullptr,
+                           bool AllowRelaxedEval = false) const;
 
   /// isPotentialConstantExpr - Return true if this function's definition
   /// might be usable in a constant expression in C++11, if it were marked
diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h
index 1e412ff6fc9e2..a3ec9ef3f3e6d 100644
--- a/clang/include/clang/Sema/Overload.h
+++ b/clang/include/clang/Sema/Overload.h
@@ -463,10 +463,11 @@ class Sema;
     }
 
     ImplicitConversionRank getRank() const;
-    NarrowingKind
-    getNarrowingKind(ASTContext &Context, const Expr *Converted,
-                     APValue &ConstantValue, QualType &ConstantType,
-                     bool IgnoreFloatToIntegralConversion = false) const;
+    NarrowingKind getNarrowingKind(ASTContext &Context, const Expr *Converted,
+                                   APValue &ConstantValue,
+                                   QualType &ConstantType,
+                                   bool IgnoreFloatToIntegralConversion = false,
+                                   bool AllowRelaxedEval = false) const;
     bool isPointerConversionToBool() const;
     bool isPointerConversionToVoidPointer(ASTContext& Context) const;
     void dump() const;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index eb09e12dd7b45..05f981f671e62 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -22728,14 +22728,15 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
 }
 
 /// Evaluate an expression as a C++11 integral constant expression.
-static bool EvaluateCPlusPlus11IntegralConstantExpr(const ASTContext &Ctx,
-                                                    const Expr *E,
-                                                    llvm::APSInt *Value) {
+static bool
+EvaluateCPlusPlus11IntegralConstantExpr(const ASTContext &Ctx, const Expr *E,
+                                        llvm::APSInt *Value,
+                                        bool AllowRelaxedEval = false) {
   if (!E->getType()->isIntegralOrUnscopedEnumerationType())
     return false;
 
   APValue Result;
-  if (!E->isCXX11ConstantExpr(Ctx, &Result))
+  if (!E->isCXX11ConstantExpr(Ctx, &Result, AllowRelaxedEval))
     return false;
 
   if (!Result.isInt())
@@ -22761,7 +22762,8 @@ bool Expr::isIntegerConstantExpr(const ASTContext &Ctx) const {
 }
 
 std::optional<llvm::APSInt>
-Expr::getIntegerConstantExpr(const ASTContext &Ctx) const {
+Expr::getIntegerConstantExpr(const ASTContext &Ctx,
+                             bool AllowRelaxedEval) const {
   if (isValueDependent()) {
     // Expression evaluator can't succeed on a dependent expression.
     return std::nullopt;
@@ -22769,7 +22771,8 @@ Expr::getIntegerConstantExpr(const ASTContext &Ctx) const {
 
   if (Ctx.getLangOpts().CPlusPlus11) {
     APSInt Value;
-    if (EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, &Value))
+    if (EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, &Value,
+                                                AllowRelaxedEval))
       return Value;
     return std::nullopt;
   }
@@ -22799,7 +22802,8 @@ bool Expr::isCXX98IntegralConstantExpr(const ASTContext &Ctx) const {
   return CheckICE(this, Ctx).Kind == IK_ICE;
 }
 
-bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result) const {
+bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result,
+                               bool AllowRelaxedEval) const {
   assert(!isValueDependent() &&
          "Expression evaluator can't be called on a dependent expression.");
 
@@ -22818,7 +22822,8 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result) const {
   // Build evaluation settings.
   Expr::EvalStatus Status;
   EvalInfo Info(Ctx, Status, EvaluationMode::ConstantExpression);
-  Status.ExtendedDiag = Ctx.MSConstExprDiag;
+  SmallVector<PartialDiagnosticAt> MSRelaxedDiag;
+  Status.ExtendedDiag = AllowRelaxedEval ? &MSRelaxedDiag : nullptr;
 
   bool IsConstExpr =
       ::EvaluateAsRValue(Info, this, Result ? *Result : Scratch) &&
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 13a2aa722de0f..97744b6e8836b 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -367,9 +367,12 @@ static const Expr *IgnoreNarrowingConversion(ASTContext &Ctx,
 ///        type of the expression prior to the narrowing conversion.
 /// \param IgnoreFloatToIntegralConversion If true type-narrowing conversions
 ///        from floating point types to integral types should be ignored.
+/// \param AllowRelaxedEval If true constant expression evaluation is relaxed
+///        to conform MSVC compiler behavior.
 NarrowingKind StandardConversionSequence::getNarrowingKind(
     ASTContext &Ctx, const Expr *Converted, APValue &ConstantValue,
-    QualType &ConstantType, bool IgnoreFloatToIntegralConversion) const {
+    QualType &ConstantType, bool IgnoreFloatToIntegralConversion,
+    bool AllowRelaxedEval) const {
   assert((Ctx.getLangOpts().CPlusPlus || Ctx.getLangOpts().C23) &&
          "narrowing check outside C++");
 
@@ -458,7 +461,8 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
       Expr::EvalResult R;
       if ((Ctx.getLangOpts().C23 && Initializer->EvaluateAsRValue(R, Ctx)) ||
           ((Ctx.getLangOpts().CPlusPlus &&
-            Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue)))) {
+            Initializer->isCXX11ConstantExpr(Ctx, &ConstantValue,
+                                             AllowRelaxedEval)))) {
         // Constant!
         if (Ctx.getLangOpts().C23)
           ConstantValue = R.Val;
@@ -548,7 +552,7 @@ NarrowingKind StandardConversionSequence::getNarrowingKind(
       return NK_Dependent_Narrowing;
 
     std::optional<llvm::APSInt> OptInitializerValue =
-        Initializer->getIntegerConstantExpr(Ctx);
+        Initializer->getIntegerConstantExpr(Ctx, AllowRelaxedEval);
     if (!OptInitializerValue) {
       // If the bit-field width was dependent, it might end up being small
       // enough to fit in the target type (unless the target type is unsigned
@@ -6598,11 +6602,14 @@ static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From,
   if (Result.isInvalid())
     return Result;
 
+  bool AllowRelaxedEval = S.getASTContext().getLangOpts().MSVCCompat;
+
   // Check for a narrowing implicit conversion.
   bool ReturnPreNarrowingValue = false;
   QualType PreNarrowingType;
-  switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue,
-                                PreNarrowingType)) {
+  switch (SCS->getNarrowingKind(
+      S.Context, Result.get(), PreNarrowingValue, PreNarrowingType,
+      /*IgnoreFloatToIntegralConversion*/ false, AllowRelaxedEval)) {
   case NK_Variable_Narrowing:
     // Implicit conversion to a narrower type, and the value is not a constant
     // expression. We'll diagnose this in a moment.
@@ -6675,10 +6682,6 @@ ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T,
                                                   CCEKind CCE,
                                                   NamedDecl *Dest) {
   APValue PreNarrowingValue;
-  SmallVector<PartialDiagnosticAt> MSWarning;
-  ASTContext &Ctx = getASTContext();
-  llvm::SaveAndRestore<SmallVector<PartialDiagnosticAt> *> SAR(
-      Ctx.MSConstExprDiag, Ctx.getLangOpts().MSVCCompat ? &MSWarning : nullptr);
   return ::BuildConvertedConstantExpression(*this, From, T, CCE, Dest,
                                             PreNarrowingValue);
 }

>From 2b06142dd731a8b0f531f3a01ac479a1bc5b1cd6 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Fri, 14 Aug 2026 19:15:05 +0200
Subject: [PATCH 24/24] Remove unneeded include file

---
 clang/include/clang/AST/ASTContext.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 80ac5fbdae047..56b51566f58f5 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -46,7 +46,6 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringSet.h"
 #include "llvm/ADT/TinyPtrVector.h"
-#include "llvm/Support/SaveAndRestore.h"
 #include "llvm/Support/TypeSize.h"
 #include <optional>
 



More information about the cfe-commits mailing list