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

>From fb1f6c372ef765bbdb948328a1341418061febdd Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Mon, 11 May 2026 19:39:23 +0200
Subject: [PATCH 1/7] [clang] Allow C-style casts in constexpr in MS compatible
 mode

Patch allows folding constant expression if -fms-compatibility is given
and the only problem found by evaluator is C-style cast. This makes it
more permissive than MSVC, which treats this expression as constant:
```
(FIELD_OFFSET(S,y) + 3) % 5
```
but doesn't do the same for this one:
```
(FIELD_OFFSET(S,y) + 3)
```
where FIELD_OFFSET is defined as:
```
```
---
 clang/include/clang/AST/ASTContext.h       |   3 +
 clang/lib/AST/ASTContext.cpp               |   6 +
 clang/lib/AST/Decl.cpp                     |   8 +-
 clang/lib/Sema/SemaExpr.cpp                |   2 +
 clang/lib/Sema/SemaOverload.cpp            |   3 +-
 clang/test/SemaCXX/microsoft-constexpr.cpp | 142 +++++++++++++++++++++
 6 files changed, 161 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/SemaCXX/microsoft-constexpr.cpp

diff --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index c45d54fdd2e88..6947f690d9197 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3873,6 +3873,9 @@ OPT_LIST(V)
   void recordMemberDataPointerEvaluation(const ValueDecl *VD);
   void recordOffsetOfEvaluation(const OffsetOfExpr *E);
 
+  bool
+  shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes);
+
 private:
   /// All OMPTraitInfo objects live in this collection, one per
   /// `pragma omp [begin] declare variant` directive.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a0894318dbd53..6e7f2645d1545 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15581,3 +15581,9 @@ void ASTContext::recordOffsetOfEvaluation(const 
OffsetOfExpr *E) {
   if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
     PFPFieldsWithEvaluatedOffset.insert(FD);
 }
+
+bool ASTContext::shouldIgnoreNotesForConstEval(
+    SmallVectorImpl<PartialDiagnosticAt> &Notes) {
+  return getLangOpts().MSVCCompat && Notes.size() == 1 &&
+         Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 535adcb2ae109..16d8a4a7728dc 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2587,8 +2587,12 @@ APValue 
*VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
   if (IsConstantInitialization &&
       (Ctx.getLangOpts().CPlusPlus ||
        (isConstexpr() && Ctx.getLangOpts().C23)) &&
-      !Notes.empty())
-    Result = false;
+      !Notes.empty()) {
+    if (!Ctx.shouldIgnoreNotesForConstEval(Notes))
+      Result = false;
+    else
+      Notes.clear();
+  }
 
   // Ensure the computed APValue is cleaned up later if evaluation succeeded,
   // or that it's empty (so that there's nothing to clean up) if evaluation
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 98062afae4577..d7c25f3a3c253 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18023,6 +18023,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, 
llvm::APSInt *Result,
   // In C++11, we can rely on diagnostics being produced for any expression
   // which is not a constant expression. If no diagnostics were produced, then
   // this is a constant expression.
+  if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+    Notes.clear();
   if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) {
     if (Result)
       *Result = EvalResult.Val.getInt();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 96c4ce489fe04..99414d28e940c 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6701,7 +6701,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, 
QualType T, APValue &Value,
     Result = ExprError();
   } else {
     Value = Eval.Val;
-
+    if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+      Notes.clear();
     if (Notes.empty()) {
       // It's a constant expression.
       Expr *E = Result.get();
diff --git a/clang/test/SemaCXX/microsoft-constexpr.cpp 
b/clang/test/SemaCXX/microsoft-constexpr.cpp
new file mode 100644
index 0000000000000..fb0a849e5ce7b
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr.cpp
@@ -0,0 +1,142 @@
+// Some of this should fail in MSVC, but work in clang
+// when -fms-compatibility is enabled.
+// RUN: %clang -fsyntax-only -fms-compatibility -std=c++20 %s
+
+typedef long LONG;
+typedef __int64 LONG_PTR, *PLONG_PTR;
+
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+  int x;
+  int y;
+};
+
+constexpr bool cb_eq  = FIELD_OFFSET(S, y) == 4;
+constexpr bool cb_ne  = FIELD_OFFSET(S, y) != 0;
+constexpr bool cb_lt  = FIELD_OFFSET(S, y) < 8;
+constexpr bool cb_le  = FIELD_OFFSET(S, y) <= 4;
+constexpr bool cb_gt  = FIELD_OFFSET(S, y) > 0;
+constexpr bool cb_ge  = FIELD_OFFSET(S, y) >= 4;
+constexpr bool cb_bool = FIELD_OFFSET(S, y);
+
+static_assert(FIELD_OFFSET(S, y) == 4);
+static_assert(FIELD_OFFSET(S, y) != 0);
+static_assert(FIELD_OFFSET(S, y) < 8);
+static_assert(FIELD_OFFSET(S, y) <= 4);
+static_assert(FIELD_OFFSET(S, y) > 0);
+static_assert(FIELD_OFFSET(S, y) >= 4);
+static_assert(FIELD_OFFSET(S, y));
+
+
+enum E {
+  enum_offset_y = FIELD_OFFSET(S, y),
+  enum_cmp_y    = FIELD_OFFSET(S, y) == 4
+};
+
+int arr_bound[FIELD_OFFSET(S, y)];
+int arr_bound_cmp[FIELD_OFFSET(S, y) == 4 ? 1 : -1];
+
+struct BitField {
+  int bf1 : FIELD_OFFSET(S, y);
+  int bf2 : FIELD_OFFSET(S, y) == 4;
+};
+
+template<int N>
+struct TplInt {};
+
+template<bool B>
+struct TplBool {};
+
+TplInt<FIELD_OFFSET(S, y)> tpl_int;
+TplBool<FIELD_OFFSET(S, y) == 4> tpl_bool;
+TplBool<FIELD_OFFSET(S, y)> tpl_bool_conv;
+
+void f() noexcept(FIELD_OFFSET(S, y) == 4) {}
+
+template<class T>
+void g() {
+  if constexpr (FIELD_OFFSET(S, y) == 4) {
+  } else {
+  }
+}
+
+struct ExplicitCtor {
+  explicit(FIELD_OFFSET(S, y) == 4) ExplicitCtor(int) {}
+};
+
+alignas(FIELD_OFFSET(S,y)) int __g;
+
+constinit int constinit_offset = FIELD_OFFSET(S, y);
+constinit bool constinit_bool = FIELD_OFFSET(S, y) == 4;
+
+constexpr int constexpr_offset = FIELD_OFFSET(S, y);
+constexpr int constexpr_cmp_as_int = FIELD_OFFSET(S, y) == 4;
+constexpr bool constexpr_bool = FIELD_OFFSET(S, y) == 4;
+
+int switch_test(int v) {
+  switch (v) {
+  case FIELD_OFFSET(S, y):
+    return 1;
+  case FIELD_OFFSET(S, x):
+    return 2;
+  default:
+    return 0;
+  }
+}
+
+template<int N = FIELD_OFFSET(S, y)>
+struct DefaultTplInt {};
+
+template<bool B = FIELD_OFFSET(S, y) == 4>
+struct DefaultTplBool {};
+
+DefaultTplInt<> default_tpl_int;
+DefaultTplBool<> default_tpl_bool;
+
+struct ArrayMember {
+  int a[FIELD_OFFSET(S, y)];
+};
+
+union U {
+  char c;
+  int a[FIELD_OFFSET(S, y)];
+};
+
+typedef char typedef_arr[FIELD_OFFSET(S, y)];
+using using_arr = char[FIELD_OFFSET(S, y)];
+
+constexpr int ternary_offset =
+    FIELD_OFFSET(S, y) == 4 ? FIELD_OFFSET(S, y) : -1;
+
+constexpr bool logical_and =
+    FIELD_OFFSET(S, y) == 4 && FIELD_OFFSET(S, x) == 0;
+
+constexpr bool logical_or =
+    FIELD_OFFSET(S, y) == 4 || FIELD_OFFSET(S, x) == 123;
+
+constexpr bool logical_not =
+    !FIELD_OFFSET(S, x);
+
+constexpr int arithmetic_add = FIELD_OFFSET(S, y) + 1;
+constexpr int arithmetic_sub = FIELD_OFFSET(S, y) - 1;
+constexpr int arithmetic_mul = FIELD_OFFSET(S, y) * 2;
+constexpr int arithmetic_div = FIELD_OFFSET(S, y) / 2;
+constexpr int arithmetic_mod = FIELD_OFFSET(S, y) % 3;
+
+constexpr int bit_or  = FIELD_OFFSET(S, y) | 1;
+constexpr int bit_and = FIELD_OFFSET(S, y) & 7;
+constexpr int bit_xor = FIELD_OFFSET(S, y) ^ 1;
+constexpr int bit_shl = FIELD_OFFSET(S, y) << 1;
+constexpr int bit_shr = FIELD_OFFSET(S, y) >> 1;
+
+constexpr int comma_expr = (0, FIELD_OFFSET(S, y));
+
+constexpr int cast_int = (int)FIELD_OFFSET(S, y);
+constexpr long cast_long = (long)FIELD_OFFSET(S, y);
+constexpr bool cast_bool = (bool)FIELD_OFFSET(S, y);
+
+template<class T, int N>
+struct DependentTpl {};
+
+DependentTpl<S, FIELD_OFFSET(S, y)> dependent_tpl;

>From a99ad36bc689431fc93470728b05e77b364f4e2a Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Tue, 12 May 2026 16:52:07 +0200
Subject: [PATCH 2/7] Add SFINAE handling and test cases

We don't want relaxed constant folding to happen during template
parameter subsititution, to avoid unexpected instantiations.
---
 clang/lib/Sema/SemaExpr.cpp                   |  8 ++++++--
 clang/lib/Sema/SemaOverload.cpp               |  5 ++++-
 .../SemaCXX/microsoft-constexpr-SFINAE.cpp    | 20 +++++++++++++++++++
 .../SemaCXX/microsoft-constexpr-SFINAE2.cpp   | 20 +++++++++++++++++++
 4 files changed, 50 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
 create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d7c25f3a3c253..51508a5093ccd 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18020,11 +18020,15 @@ Sema::VerifyIntegerConstantExpression(Expr *E, 
llvm::APSInt *Result,
   if (!isa<ConstantExpr>(E))
     E = ConstantExpr::Create(Context, E, EvalResult.Val);
 
+  // For -fms-compatibility mode we relax some requirements
+  // for constant folding in non-SFINAE contexts
+  if (!isSFINAEContext() &&
+      getASTContext().shouldIgnoreNotesForConstEval(Notes))
+    Notes.clear();
+
   // In C++11, we can rely on diagnostics being produced for any expression
   // which is not a constant expression. If no diagnostics were produced, then
   // this is a constant expression.
-  if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
-    Notes.clear();
   if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) {
     if (Result)
       *Result = EvalResult.Val.getInt();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 99414d28e940c..db471ef8fca17 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6701,7 +6701,10 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, 
QualType T, APValue &Value,
     Result = ExprError();
   } else {
     Value = Eval.Val;
-    if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+    // For -fms-compatibility mode we relax some requirements
+    // for constant folding in non-SFINAE contexts
+    if (!isSFINAEContext() &&
+        getASTContext().shouldIgnoreNotesForConstEval(Notes))
       Notes.clear();
     if (Notes.empty()) {
       // It's a constant expression.
diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp 
b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
new file mode 100644
index 0000000000000..271d1bcfad99e
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -triple 
x86_64-windows-msvc %s
+
+typedef long long LONG_PTR;
+typedef long LONG;
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+  int x;
+  int y;
+};
+
+template<class T, LONG_PTR = FIELD_OFFSET(S, y)>
+char probe(int);
+
+template<class>
+long probe(...);
+
+static_assert(sizeof(probe<int>(0)) == sizeof(char), "");
+// expected-error@-1 {{static assertion failed due to requirement 'sizeof 
(probe<int>(0)) == sizeof(char)'}}
+// expected-note@-2 {{expression evaluates to '4 == 1'}}
diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp 
b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp
new file mode 100644
index 0000000000000..b63ea73eaab28
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -fms-compatibility -triple 
x86_64-windows-msvc -verify %s
+
+typedef long long LONG_PTR;
+typedef long LONG;
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+  int x;
+  int y;
+};
+
+template<class T, bool = __builtin_choose_expr(FIELD_OFFSET(T, y) > 0, true, 
false)>
+char probe(int);
+
+template<class>
+long probe(...);
+
+static_assert(sizeof(probe<S>(0)) == sizeof(char), "");
+// expected-error@-1 {{static assertion failed due to requirement 'sizeof 
(probe<S>(0)) == sizeof(char)'}}
+// expected-note@-2 {{expression evaluates to '4 == 1'}}

>From d94eb4115127dc3c4192b1a2e35ae57b8f2028d7 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Tue, 12 May 2026 19:22:43 +0200
Subject: [PATCH 3/7] Add opt-in warning for MS relaxed constant folding

---
 clang/include/clang/AST/ASTContext.h            |  3 +--
 clang/include/clang/Basic/DiagnosticASTKinds.td |  4 ++++
 clang/include/clang/Basic/DiagnosticGroups.td   |  2 ++
 clang/lib/AST/ASTContext.cpp                    | 11 ++++++++---
 clang/lib/AST/Decl.cpp                          |  4 +---
 clang/lib/Sema/SemaExpr.cpp                     |  5 ++---
 clang/lib/Sema/SemaOverload.cpp                 |  5 ++---
 clang/test/SemaCXX/microsoft-constexpr2.cpp     | 12 ++++++++++++
 8 files changed, 32 insertions(+), 14 deletions(-)
 create mode 100644 clang/test/SemaCXX/microsoft-constexpr2.cpp

diff --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index 6947f690d9197..951af8a604a85 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3873,8 +3873,7 @@ OPT_LIST(V)
   void recordMemberDataPointerEvaluation(const ValueDecl *VD);
   void recordOffsetOfEvaluation(const OffsetOfExpr *E);
 
-  bool
-  shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes);
+  bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes);
 
 private:
   /// All OMPTraitInfo objects live in this collection, one per
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td 
b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..b7252902c969e 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1030,6 +1030,10 @@ def warn_npot_ms_struct : Warning<
   "ms_struct may not produce Microsoft-compatible layouts with fundamental "
   "data types with sizes that aren't a power of two">,
   DefaultError, InGroup<IncompatibleMSStruct>;
+def warn_relaxed_constant_fold : Warning<
+  "folding this constant expression is a Microsoft extension">,
+  InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore;
+
 
 def err_itanium_layout_unimplemented : Error<
   "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td 
b/clang/include/clang/Basic/DiagnosticGroups.td
index 2b3055d6d6bdd..c548ab2e67d4c 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1619,6 +1619,8 @@ def MicrosoftStringLiteralFromPredefined : DiagGroup<
     "microsoft-string-literal-from-predefined">;
 def MicrosoftInlineOnNonFunction : DiagGroup<
     "microsoft-inline-on-non-function">;
+def MicrosoftRelaxedConstantFold :
+  DiagGroup<"relaxed-constant-fold">;
 
 // Aliases.
 def : DiagGroup<"msvc-include", [MicrosoftInclude]>;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6e7f2645d1545..c5195ba4dcbc2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15582,8 +15582,13 @@ void ASTContext::recordOffsetOfEvaluation(const 
OffsetOfExpr *E) {
     PFPFieldsWithEvaluatedOffset.insert(FD);
 }
 
-bool ASTContext::shouldIgnoreNotesForConstEval(
+bool ASTContext::maybeFoldMSConstexpr(
     SmallVectorImpl<PartialDiagnosticAt> &Notes) {
-  return getLangOpts().MSVCCompat && Notes.size() == 1 &&
-         Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+  bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 &&
+              Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+  if (Fold) {
+    getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
+    Notes.clear();
+  }
+  return Fold;
 }
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 16d8a4a7728dc..1c23dbca7cd4e 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2588,10 +2588,8 @@ APValue 
*VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
       (Ctx.getLangOpts().CPlusPlus ||
        (isConstexpr() && Ctx.getLangOpts().C23)) &&
       !Notes.empty()) {
-    if (!Ctx.shouldIgnoreNotesForConstEval(Notes))
+    if (!Ctx.maybeFoldMSConstexpr(Notes))
       Result = false;
-    else
-      Notes.clear();
   }
 
   // Ensure the computed APValue is cleaned up later if evaluation succeeded,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 51508a5093ccd..e4da73a54d32e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18022,9 +18022,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, 
llvm::APSInt *Result,
 
   // For -fms-compatibility mode we relax some requirements
   // for constant folding in non-SFINAE contexts
-  if (!isSFINAEContext() &&
-      getASTContext().shouldIgnoreNotesForConstEval(Notes))
-    Notes.clear();
+  if (!isSFINAEContext())
+    getASTContext().maybeFoldMSConstexpr(Notes);
 
   // In C++11, we can rely on diagnostics being produced for any expression
   // which is not a constant expression. If no diagnostics were produced, then
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index db471ef8fca17..e80b17ab6634f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6703,9 +6703,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, 
QualType T, APValue &Value,
     Value = Eval.Val;
     // For -fms-compatibility mode we relax some requirements
     // for constant folding in non-SFINAE contexts
-    if (!isSFINAEContext() &&
-        getASTContext().shouldIgnoreNotesForConstEval(Notes))
-      Notes.clear();
+    if (!isSFINAEContext())
+      getASTContext().maybeFoldMSConstexpr(Notes);
     if (Notes.empty()) {
       // It's a constant expression.
       Expr *E = Result.get();
diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp 
b/clang/test/SemaCXX/microsoft-constexpr2.cpp
new file mode 100644
index 0000000000000..25d4b3ed2b3f3
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility 
-Wrelaxed-constant-fold %s
+
+typedef long long LONG_PTR;
+typedef long LONG;
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+  int x;
+  int y;
+};
+
+constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this 
constant expression is a Microsoft extension}}

>From ae7a6aa78f7a06fca3979443b1e064e0c3df4ebe Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Thu, 14 May 2026 17:00:59 +0200
Subject: [PATCH 4/7] Change definition from Warning to Extension

---
 clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td 
b/clang/include/clang/Basic/DiagnosticASTKinds.td
index b7252902c969e..192eb85b019cf 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1030,9 +1030,9 @@ def warn_npot_ms_struct : Warning<
   "ms_struct may not produce Microsoft-compatible layouts with fundamental "
   "data types with sizes that aren't a power of two">,
   DefaultError, InGroup<IncompatibleMSStruct>;
-def warn_relaxed_constant_fold : Warning<
+def warn_relaxed_constant_fold : Extension<
   "folding this constant expression is a Microsoft extension">,
-  InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore;
+  InGroup<MicrosoftRelaxedConstantFold>;
 
 
 def err_itanium_layout_unimplemented : Error<

>From 3844bb95267033e6860b5a61243909bfbe6b6107 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Thu, 14 May 2026 17:07:02 +0200
Subject: [PATCH 5/7] Don't allow folding constant expressions using
 dynamic_cast

This can be done with -std=c++20, so we don't need to this in Microsoft
compatibility mode.
---
 clang/include/clang/Basic/PartialDiagnostic.h |  8 ++++++++
 clang/lib/AST/ASTContext.cpp                  |  7 +++++--
 clang/test/SemaCXX/microsoft-constexpr3.cpp   | 18 ++++++++++++++++++
 3 files changed, 31 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/SemaCXX/microsoft-constexpr3.cpp

diff --git a/clang/include/clang/Basic/PartialDiagnostic.h 
b/clang/include/clang/Basic/PartialDiagnostic.h
index 4bf6049d08fdb..7658bfa3795f4 100644
--- a/clang/include/clang/Basic/PartialDiagnostic.h
+++ b/clang/include/clang/Basic/PartialDiagnostic.h
@@ -189,6 +189,14 @@ class PartialDiagnostic : public StreamingDiagnostic {
              == DiagnosticsEngine::ak_std_string && "Not a string arg");
     return DiagStorage->DiagArgumentsStr[I];
   }
+  uint64_t getValueArg(unsigned I) {
+    assert(DiagStorage && "No diagnostic storage?");
+    assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
+    assert(DiagStorage->DiagArgumentsKind[I] !=
+               DiagnosticsEngine::ak_std_string &&
+           "Not an integer arg");
+    return DiagStorage->DiagArgumentsVal[I];
+  }
 };
 
 inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c5195ba4dcbc2..6430bfa56159f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15584,8 +15584,11 @@ void ASTContext::recordOffsetOfEvaluation(const 
OffsetOfExpr *E) {
 
 bool ASTContext::maybeFoldMSConstexpr(
     SmallVectorImpl<PartialDiagnosticAt> &Notes) {
-  bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 &&
-              Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+  bool Fold =
+      getLangOpts().MSVCCompat && Notes.size() == 1 &&
+      Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast &&
+      Notes[0].second.getValueArg(0) != 
diag::ConstexprInvalidCastKind::Dynamic;
+
   if (Fold) {
     getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
     Notes.clear();
diff --git a/clang/test/SemaCXX/microsoft-constexpr3.cpp 
b/clang/test/SemaCXX/microsoft-constexpr3.cpp
new file mode 100644
index 0000000000000..dd52bb8f81d32
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr3.cpp
@@ -0,0 +1,18 @@
+// Ignore dynamic_cast when relaxing constant expression with 
-fms-compatibility
+// However using dynamic_cast is still possible in c++20 and higher
+// RUN: not %clang_cc1 -std=c++11 -fms-compatibility -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++20 -fms-compatibility -fsyntax-only %s
+
+struct B {
+  virtual ~B() {}
+};
+
+struct D : B {
+  int x = 123;
+};
+
+#define IsD(x) (dynamic_cast<const D*>(x) != 0)
+
+static const D od;
+
+constexpr bool is_d = IsD(&od);

>From c66b4ad9bdcdab1bb80270fd293ce6c07a505415 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Thu, 14 May 2026 20:02:28 +0200
Subject: [PATCH 6/7] Improved diagnostics

---
 .../include/clang/Basic/DiagnosticASTKinds.td |  5 ++--
 clang/include/clang/Basic/PartialDiagnostic.h |  2 +-
 clang/lib/AST/ASTContext.cpp                  | 30 +++++++++++++------
 clang/test/SemaCXX/microsoft-constexpr2.cpp   |  4 ++-
 4 files changed, 28 insertions(+), 13 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td 
b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 192eb85b019cf..7f0f0a503a8ba 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1031,10 +1031,11 @@ def warn_npot_ms_struct : Warning<
   "data types with sizes that aren't a power of two">,
   DefaultError, InGroup<IncompatibleMSStruct>;
 def warn_relaxed_constant_fold : Extension<
-  "folding this constant expression is a Microsoft extension">,
+  "folding constant expression involving "
+  "%select{reinterpret_cast|cast that performs the conversions of a 
reinterpret_cast}0"
+  " is a Microsoft extension">,
   InGroup<MicrosoftRelaxedConstantFold>;
 
-
 def err_itanium_layout_unimplemented : Error<
   "Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">;
 
diff --git a/clang/include/clang/Basic/PartialDiagnostic.h 
b/clang/include/clang/Basic/PartialDiagnostic.h
index 7658bfa3795f4..7469e45f7d888 100644
--- a/clang/include/clang/Basic/PartialDiagnostic.h
+++ b/clang/include/clang/Basic/PartialDiagnostic.h
@@ -194,7 +194,7 @@ class PartialDiagnostic : public StreamingDiagnostic {
     assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
     assert(DiagStorage->DiagArgumentsKind[I] !=
                DiagnosticsEngine::ak_std_string &&
-           "Not an integer arg");
+           "Not a value arg");
     return DiagStorage->DiagArgumentsVal[I];
   }
 };
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6430bfa56159f..c779b72298895 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15584,14 +15584,26 @@ void ASTContext::recordOffsetOfEvaluation(const 
OffsetOfExpr *E) {
 
 bool ASTContext::maybeFoldMSConstexpr(
     SmallVectorImpl<PartialDiagnosticAt> &Notes) {
-  bool Fold =
-      getLangOpts().MSVCCompat && Notes.size() == 1 &&
-      Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast &&
-      Notes[0].second.getValueArg(0) != 
diag::ConstexprInvalidCastKind::Dynamic;
-
-  if (Fold) {
-    getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
-    Notes.clear();
+  if (Notes.size() != 1 || !getLangOpts().MSVCCompat)
+    return false;
+  auto &PD = Notes[0].second;
+  if (PD.getDiagID() != diag::note_constexpr_invalid_cast)
+    return false;
+  unsigned CastID;
+  switch (PD.getValueArg(0)) {
+  case diag::ConstexprInvalidCastKind::Reinterpret:
+    CastID = 0;
+    break;
+  case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret:
+    if (!PD.getValueArg(1))
+      return false;
+    CastID = 1;
+    break;
+  default:
+    return false;
   }
-  return Fold;
+  getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold)
+      << CastID;
+  Notes.clear();
+  return true;
 }
diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp 
b/clang/test/SemaCXX/microsoft-constexpr2.cpp
index 25d4b3ed2b3f3..e756a0240d5fe 100644
--- a/clang/test/SemaCXX/microsoft-constexpr2.cpp
+++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp
@@ -3,10 +3,12 @@
 typedef long long LONG_PTR;
 typedef long LONG;
 #define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+#define FIELD_OFFSET2(type, field) (reinterpret_cast<LONG_PTR>(&(((type 
*)0)->field)))
 
 struct S {
   int x;
   int y;
 };
 
-constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this 
constant expression is a Microsoft extension}}
+constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant 
expression involving cast that performs the conversions of a reinterpret_cast 
is a Microsoft extension}}
+constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding 
constant expression involving reinterpret_cast is a Microsoft extension}}

>From 15d7438b5764456904270f3f6866377b2f74ec79 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <[email protected]>
Date: Mon, 18 May 2026 13:50:18 +0200
Subject: [PATCH 7/7] Add note to distinguish ptr to int conversions

---
 .../include/clang/Basic/DiagnosticASTKinds.td  |  4 ++++
 clang/lib/AST/ASTContext.cpp                   | 18 +++---------------
 clang/lib/AST/ExprConstant.cpp                 |  4 ++--
 3 files changed, 9 insertions(+), 17 deletions(-)

diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td 
b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 7f0f0a503a8ba..ce864bdb1fe8a 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -16,6 +16,10 @@ def note_constexpr_invalid_cast : Note<
   "of a reinterpret_cast}1}|%CastFrom{cast from %1}}0"
   " is not allowed in a constant expression"
   "%select{| in C++ standards before C++20||}0">;
+def note_constexpr_invalid_cast_ptrtoint : Note<
+  "%select{reinterpret_cast||"
+  "%select{this conversion|cast that performs the conversions of a 
reinterpret_cast}1|"
+  "}0 is not allowed in a constant expression">;
 def note_constexpr_invalid_void_star_cast : Note<
   "cast from %0 is not allowed in a constant expression "
   "%select{in C++ standards before C++2c|because the pointed object "
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c779b72298895..89f3b3ffca83f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15587,23 +15587,11 @@ bool ASTContext::maybeFoldMSConstexpr(
   if (Notes.size() != 1 || !getLangOpts().MSVCCompat)
     return false;
   auto &PD = Notes[0].second;
-  if (PD.getDiagID() != diag::note_constexpr_invalid_cast)
+  if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint)
     return false;
-  unsigned CastID;
-  switch (PD.getValueArg(0)) {
-  case diag::ConstexprInvalidCastKind::Reinterpret:
-    CastID = 0;
-    break;
-  case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret:
-    if (!PD.getValueArg(1))
-      return false;
-    CastID = 1;
-    break;
-  default:
-    return false;
-  }
   getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold)
-      << CastID;
+      << !!PD.getValueArg(0);
   Notes.clear();
+
   return true;
 }
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f3a80f5b77a3..1110b67ab15b2 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8533,7 +8533,7 @@ class ExprEvaluatorBase
   }
 
   bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) {
-    CCEDiag(E, diag::note_constexpr_invalid_cast)
+    CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint)
         << diag::ConstexprInvalidCastKind::Reinterpret;
     return static_cast<Derived*>(this)->VisitCastExpr(E);
   }
@@ -19450,7 +19450,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) 
{
   }
 
   case CK_PointerToIntegral: {
-    CCEDiag(E, diag::note_constexpr_invalid_cast)
+    CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint)
         << diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret
         << Info.Ctx.getLangOpts().CPlusPlus << E->getSourceRange();
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to