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