[clang] [clang] Allow C-style casts in constexpr in MS compatible mode (PR #197005)
via cfe-commits
cfe-commits at lists.llvm.org
Tue May 19 05:50:19 PDT 2026
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 <eleviant at accesssoftek.com>
Date: Mon, 11 May 2026 19:39:23 +0200
Subject: [PATCH 1/8] [clang] Allow C-style casts in constexpr in MS compatible
mode
Patch allows folding constant expression if -fms-compatibility is given
and the only problem found by evaluator is C-style cast. This makes it
more permissive than MSVC, which treats this expression as constant:
```
(FIELD_OFFSET(S,y) + 3) % 5
```
but doesn't do the same for this one:
```
(FIELD_OFFSET(S,y) + 3)
```
where FIELD_OFFSET is defined as:
```
```
---
clang/include/clang/AST/ASTContext.h | 3 +
clang/lib/AST/ASTContext.cpp | 6 +
clang/lib/AST/Decl.cpp | 8 +-
clang/lib/Sema/SemaExpr.cpp | 2 +
clang/lib/Sema/SemaOverload.cpp | 3 +-
clang/test/SemaCXX/microsoft-constexpr.cpp | 142 +++++++++++++++++++++
6 files changed, 161 insertions(+), 3 deletions(-)
create mode 100644 clang/test/SemaCXX/microsoft-constexpr.cpp
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index c45d54fdd2e88..6947f690d9197 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3873,6 +3873,9 @@ OPT_LIST(V)
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
+ bool
+ shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes);
+
private:
/// All OMPTraitInfo objects live in this collection, one per
/// `pragma omp [begin] declare variant` directive.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index a0894318dbd53..6e7f2645d1545 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15581,3 +15581,9 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
if (FieldDecl *FD = Comp.getField(); isPFPField(FD))
PFPFieldsWithEvaluatedOffset.insert(FD);
}
+
+bool ASTContext::shouldIgnoreNotesForConstEval(
+ SmallVectorImpl<PartialDiagnosticAt> &Notes) {
+ return getLangOpts().MSVCCompat && Notes.size() == 1 &&
+ Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 535adcb2ae109..16d8a4a7728dc 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2587,8 +2587,12 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
if (IsConstantInitialization &&
(Ctx.getLangOpts().CPlusPlus ||
(isConstexpr() && Ctx.getLangOpts().C23)) &&
- !Notes.empty())
- Result = false;
+ !Notes.empty()) {
+ if (!Ctx.shouldIgnoreNotesForConstEval(Notes))
+ Result = false;
+ else
+ Notes.clear();
+ }
// Ensure the computed APValue is cleaned up later if evaluation succeeded,
// or that it's empty (so that there's nothing to clean up) if evaluation
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 98062afae4577..d7c25f3a3c253 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18023,6 +18023,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
// In C++11, we can rely on diagnostics being produced for any expression
// which is not a constant expression. If no diagnostics were produced, then
// this is a constant expression.
+ if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+ Notes.clear();
if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) {
if (Result)
*Result = EvalResult.Val.getInt();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 96c4ce489fe04..99414d28e940c 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6701,7 +6701,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
Result = ExprError();
} else {
Value = Eval.Val;
-
+ if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+ Notes.clear();
if (Notes.empty()) {
// It's a constant expression.
Expr *E = Result.get();
diff --git a/clang/test/SemaCXX/microsoft-constexpr.cpp b/clang/test/SemaCXX/microsoft-constexpr.cpp
new file mode 100644
index 0000000000000..fb0a849e5ce7b
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr.cpp
@@ -0,0 +1,142 @@
+// Some of this should fail in MSVC, but work in clang
+// when -fms-compatibility is enabled.
+// RUN: %clang -fsyntax-only -fms-compatibility -std=c++20 %s
+
+typedef long LONG;
+typedef __int64 LONG_PTR, *PLONG_PTR;
+
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+ int x;
+ int y;
+};
+
+constexpr bool cb_eq = FIELD_OFFSET(S, y) == 4;
+constexpr bool cb_ne = FIELD_OFFSET(S, y) != 0;
+constexpr bool cb_lt = FIELD_OFFSET(S, y) < 8;
+constexpr bool cb_le = FIELD_OFFSET(S, y) <= 4;
+constexpr bool cb_gt = FIELD_OFFSET(S, y) > 0;
+constexpr bool cb_ge = FIELD_OFFSET(S, y) >= 4;
+constexpr bool cb_bool = FIELD_OFFSET(S, y);
+
+static_assert(FIELD_OFFSET(S, y) == 4);
+static_assert(FIELD_OFFSET(S, y) != 0);
+static_assert(FIELD_OFFSET(S, y) < 8);
+static_assert(FIELD_OFFSET(S, y) <= 4);
+static_assert(FIELD_OFFSET(S, y) > 0);
+static_assert(FIELD_OFFSET(S, y) >= 4);
+static_assert(FIELD_OFFSET(S, y));
+
+
+enum E {
+ enum_offset_y = FIELD_OFFSET(S, y),
+ enum_cmp_y = FIELD_OFFSET(S, y) == 4
+};
+
+int arr_bound[FIELD_OFFSET(S, y)];
+int arr_bound_cmp[FIELD_OFFSET(S, y) == 4 ? 1 : -1];
+
+struct BitField {
+ int bf1 : FIELD_OFFSET(S, y);
+ int bf2 : FIELD_OFFSET(S, y) == 4;
+};
+
+template<int N>
+struct TplInt {};
+
+template<bool B>
+struct TplBool {};
+
+TplInt<FIELD_OFFSET(S, y)> tpl_int;
+TplBool<FIELD_OFFSET(S, y) == 4> tpl_bool;
+TplBool<FIELD_OFFSET(S, y)> tpl_bool_conv;
+
+void f() noexcept(FIELD_OFFSET(S, y) == 4) {}
+
+template<class T>
+void g() {
+ if constexpr (FIELD_OFFSET(S, y) == 4) {
+ } else {
+ }
+}
+
+struct ExplicitCtor {
+ explicit(FIELD_OFFSET(S, y) == 4) ExplicitCtor(int) {}
+};
+
+alignas(FIELD_OFFSET(S,y)) int __g;
+
+constinit int constinit_offset = FIELD_OFFSET(S, y);
+constinit bool constinit_bool = FIELD_OFFSET(S, y) == 4;
+
+constexpr int constexpr_offset = FIELD_OFFSET(S, y);
+constexpr int constexpr_cmp_as_int = FIELD_OFFSET(S, y) == 4;
+constexpr bool constexpr_bool = FIELD_OFFSET(S, y) == 4;
+
+int switch_test(int v) {
+ switch (v) {
+ case FIELD_OFFSET(S, y):
+ return 1;
+ case FIELD_OFFSET(S, x):
+ return 2;
+ default:
+ return 0;
+ }
+}
+
+template<int N = FIELD_OFFSET(S, y)>
+struct DefaultTplInt {};
+
+template<bool B = FIELD_OFFSET(S, y) == 4>
+struct DefaultTplBool {};
+
+DefaultTplInt<> default_tpl_int;
+DefaultTplBool<> default_tpl_bool;
+
+struct ArrayMember {
+ int a[FIELD_OFFSET(S, y)];
+};
+
+union U {
+ char c;
+ int a[FIELD_OFFSET(S, y)];
+};
+
+typedef char typedef_arr[FIELD_OFFSET(S, y)];
+using using_arr = char[FIELD_OFFSET(S, y)];
+
+constexpr int ternary_offset =
+ FIELD_OFFSET(S, y) == 4 ? FIELD_OFFSET(S, y) : -1;
+
+constexpr bool logical_and =
+ FIELD_OFFSET(S, y) == 4 && FIELD_OFFSET(S, x) == 0;
+
+constexpr bool logical_or =
+ FIELD_OFFSET(S, y) == 4 || FIELD_OFFSET(S, x) == 123;
+
+constexpr bool logical_not =
+ !FIELD_OFFSET(S, x);
+
+constexpr int arithmetic_add = FIELD_OFFSET(S, y) + 1;
+constexpr int arithmetic_sub = FIELD_OFFSET(S, y) - 1;
+constexpr int arithmetic_mul = FIELD_OFFSET(S, y) * 2;
+constexpr int arithmetic_div = FIELD_OFFSET(S, y) / 2;
+constexpr int arithmetic_mod = FIELD_OFFSET(S, y) % 3;
+
+constexpr int bit_or = FIELD_OFFSET(S, y) | 1;
+constexpr int bit_and = FIELD_OFFSET(S, y) & 7;
+constexpr int bit_xor = FIELD_OFFSET(S, y) ^ 1;
+constexpr int bit_shl = FIELD_OFFSET(S, y) << 1;
+constexpr int bit_shr = FIELD_OFFSET(S, y) >> 1;
+
+constexpr int comma_expr = (0, FIELD_OFFSET(S, y));
+
+constexpr int cast_int = (int)FIELD_OFFSET(S, y);
+constexpr long cast_long = (long)FIELD_OFFSET(S, y);
+constexpr bool cast_bool = (bool)FIELD_OFFSET(S, y);
+
+template<class T, int N>
+struct DependentTpl {};
+
+DependentTpl<S, FIELD_OFFSET(S, y)> dependent_tpl;
>From a99ad36bc689431fc93470728b05e77b364f4e2a Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 12 May 2026 16:52:07 +0200
Subject: [PATCH 2/8] Add SFINAE handling and test cases
We don't want relaxed constant folding to happen during template
parameter subsititution, to avoid unexpected instantiations.
---
clang/lib/Sema/SemaExpr.cpp | 8 ++++++--
clang/lib/Sema/SemaOverload.cpp | 5 ++++-
.../SemaCXX/microsoft-constexpr-SFINAE.cpp | 20 +++++++++++++++++++
.../SemaCXX/microsoft-constexpr-SFINAE2.cpp | 20 +++++++++++++++++++
4 files changed, 50 insertions(+), 3 deletions(-)
create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
create mode 100644 clang/test/SemaCXX/microsoft-constexpr-SFINAE2.cpp
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index d7c25f3a3c253..51508a5093ccd 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18020,11 +18020,15 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
if (!isa<ConstantExpr>(E))
E = ConstantExpr::Create(Context, E, EvalResult.Val);
+ // For -fms-compatibility mode we relax some requirements
+ // for constant folding in non-SFINAE contexts
+ if (!isSFINAEContext() &&
+ getASTContext().shouldIgnoreNotesForConstEval(Notes))
+ Notes.clear();
+
// In C++11, we can rely on diagnostics being produced for any expression
// which is not a constant expression. If no diagnostics were produced, then
// this is a constant expression.
- if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
- Notes.clear();
if (Folded && getLangOpts().CPlusPlus11 && Notes.empty()) {
if (Result)
*Result = EvalResult.Val.getInt();
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index 99414d28e940c..db471ef8fca17 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6701,7 +6701,10 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
Result = ExprError();
} else {
Value = Eval.Val;
- if (getASTContext().shouldIgnoreNotesForConstEval(Notes))
+ // For -fms-compatibility mode we relax some requirements
+ // for constant folding in non-SFINAE contexts
+ if (!isSFINAEContext() &&
+ getASTContext().shouldIgnoreNotesForConstEval(Notes))
Notes.clear();
if (Notes.empty()) {
// It's a constant expression.
diff --git a/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
new file mode 100644
index 0000000000000..271d1bcfad99e
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr-SFINAE.cpp
@@ -0,0 +1,20 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -triple x86_64-windows-msvc %s
+
+typedef long long LONG_PTR;
+typedef long LONG;
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+ int x;
+ int y;
+};
+
+template<class T, LONG_PTR = FIELD_OFFSET(S, y)>
+char probe(int);
+
+template<class>
+long probe(...);
+
+static_assert(sizeof(probe<int>(0)) == sizeof(char), "");
+// expected-error 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 d94eb4115127dc3c4192b1a2e35ae57b8f2028d7 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 3/8] Add opt-in warning for MS relaxed constant folding
---
clang/include/clang/AST/ASTContext.h | 3 +--
clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++++
clang/include/clang/Basic/DiagnosticGroups.td | 2 ++
clang/lib/AST/ASTContext.cpp | 11 ++++++++---
clang/lib/AST/Decl.cpp | 4 +---
clang/lib/Sema/SemaExpr.cpp | 5 ++---
clang/lib/Sema/SemaOverload.cpp | 5 ++---
clang/test/SemaCXX/microsoft-constexpr2.cpp | 12 ++++++++++++
8 files changed, 32 insertions(+), 14 deletions(-)
create mode 100644 clang/test/SemaCXX/microsoft-constexpr2.cpp
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 6947f690d9197..951af8a604a85 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3873,8 +3873,7 @@ OPT_LIST(V)
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
- bool
- shouldIgnoreNotesForConstEval(SmallVectorImpl<PartialDiagnosticAt> &Notes);
+ bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes);
private:
/// All OMPTraitInfo objects live in this collection, one per
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index bde418695f647..b7252902c969e 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1030,6 +1030,10 @@ def warn_npot_ms_struct : Warning<
"ms_struct may not produce Microsoft-compatible layouts with fundamental "
"data types with sizes that aren't a power of two">,
DefaultError, InGroup<IncompatibleMSStruct>;
+def warn_relaxed_constant_fold : Warning<
+ "folding this constant expression is a Microsoft extension">,
+ InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore;
+
def err_itanium_layout_unimplemented : Error<
"Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">;
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index 2b3055d6d6bdd..c548ab2e67d4c 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -1619,6 +1619,8 @@ def MicrosoftStringLiteralFromPredefined : DiagGroup<
"microsoft-string-literal-from-predefined">;
def MicrosoftInlineOnNonFunction : DiagGroup<
"microsoft-inline-on-non-function">;
+def MicrosoftRelaxedConstantFold :
+ DiagGroup<"relaxed-constant-fold">;
// Aliases.
def : DiagGroup<"msvc-include", [MicrosoftInclude]>;
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6e7f2645d1545..c5195ba4dcbc2 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15582,8 +15582,13 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
PFPFieldsWithEvaluatedOffset.insert(FD);
}
-bool ASTContext::shouldIgnoreNotesForConstEval(
+bool ASTContext::maybeFoldMSConstexpr(
SmallVectorImpl<PartialDiagnosticAt> &Notes) {
- return getLangOpts().MSVCCompat && Notes.size() == 1 &&
- Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+ bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 &&
+ Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+ if (Fold) {
+ getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
+ Notes.clear();
+ }
+ return Fold;
}
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 16d8a4a7728dc..1c23dbca7cd4e 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2588,10 +2588,8 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
(Ctx.getLangOpts().CPlusPlus ||
(isConstexpr() && Ctx.getLangOpts().C23)) &&
!Notes.empty()) {
- if (!Ctx.shouldIgnoreNotesForConstEval(Notes))
+ if (!Ctx.maybeFoldMSConstexpr(Notes))
Result = false;
- else
- Notes.clear();
}
// Ensure the computed APValue is cleaned up later if evaluation succeeded,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 51508a5093ccd..e4da73a54d32e 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18022,9 +18022,8 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
// For -fms-compatibility mode we relax some requirements
// for constant folding in non-SFINAE contexts
- if (!isSFINAEContext() &&
- getASTContext().shouldIgnoreNotesForConstEval(Notes))
- Notes.clear();
+ if (!isSFINAEContext())
+ getASTContext().maybeFoldMSConstexpr(Notes);
// In C++11, we can rely on diagnostics being produced for any expression
// which is not a constant expression. If no diagnostics were produced, then
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index db471ef8fca17..e80b17ab6634f 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6703,9 +6703,8 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
Value = Eval.Val;
// For -fms-compatibility mode we relax some requirements
// for constant folding in non-SFINAE contexts
- if (!isSFINAEContext() &&
- getASTContext().shouldIgnoreNotesForConstEval(Notes))
- Notes.clear();
+ if (!isSFINAEContext())
+ getASTContext().maybeFoldMSConstexpr(Notes);
if (Notes.empty()) {
// It's a constant expression.
Expr *E = Result.get();
diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp
new file mode 100644
index 0000000000000..25d4b3ed2b3f3
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fms-compatibility -Wrelaxed-constant-fold %s
+
+typedef long long LONG_PTR;
+typedef long LONG;
+#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+
+struct S {
+ int x;
+ int y;
+};
+
+constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}}
>From ae7a6aa78f7a06fca3979443b1e064e0c3df4ebe Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 17:00:59 +0200
Subject: [PATCH 4/8] Change definition from Warning to Extension
---
clang/include/clang/Basic/DiagnosticASTKinds.td | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index b7252902c969e..192eb85b019cf 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1030,9 +1030,9 @@ def warn_npot_ms_struct : Warning<
"ms_struct may not produce Microsoft-compatible layouts with fundamental "
"data types with sizes that aren't a power of two">,
DefaultError, InGroup<IncompatibleMSStruct>;
-def warn_relaxed_constant_fold : Warning<
+def warn_relaxed_constant_fold : Extension<
"folding this constant expression is a Microsoft extension">,
- InGroup<MicrosoftRelaxedConstantFold>, DefaultIgnore;
+ InGroup<MicrosoftRelaxedConstantFold>;
def err_itanium_layout_unimplemented : Error<
>From 3844bb95267033e6860b5a61243909bfbe6b6107 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 17:07:02 +0200
Subject: [PATCH 5/8] Don't allow folding constant expressions using
dynamic_cast
This can be done with -std=c++20, so we don't need to this in Microsoft
compatibility mode.
---
clang/include/clang/Basic/PartialDiagnostic.h | 8 ++++++++
clang/lib/AST/ASTContext.cpp | 7 +++++--
clang/test/SemaCXX/microsoft-constexpr3.cpp | 18 ++++++++++++++++++
3 files changed, 31 insertions(+), 2 deletions(-)
create mode 100644 clang/test/SemaCXX/microsoft-constexpr3.cpp
diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h
index 4bf6049d08fdb..7658bfa3795f4 100644
--- a/clang/include/clang/Basic/PartialDiagnostic.h
+++ b/clang/include/clang/Basic/PartialDiagnostic.h
@@ -189,6 +189,14 @@ class PartialDiagnostic : public StreamingDiagnostic {
== DiagnosticsEngine::ak_std_string && "Not a string arg");
return DiagStorage->DiagArgumentsStr[I];
}
+ uint64_t getValueArg(unsigned I) {
+ assert(DiagStorage && "No diagnostic storage?");
+ assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
+ assert(DiagStorage->DiagArgumentsKind[I] !=
+ DiagnosticsEngine::ak_std_string &&
+ "Not an integer arg");
+ return DiagStorage->DiagArgumentsVal[I];
+ }
};
inline const DiagnosticBuilder &operator<<(const DiagnosticBuilder &DB,
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c5195ba4dcbc2..6430bfa56159f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15584,8 +15584,11 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
bool ASTContext::maybeFoldMSConstexpr(
SmallVectorImpl<PartialDiagnosticAt> &Notes) {
- bool Fold = getLangOpts().MSVCCompat && Notes.size() == 1 &&
- Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast;
+ bool Fold =
+ getLangOpts().MSVCCompat && Notes.size() == 1 &&
+ Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast &&
+ Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic;
+
if (Fold) {
getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
Notes.clear();
diff --git a/clang/test/SemaCXX/microsoft-constexpr3.cpp b/clang/test/SemaCXX/microsoft-constexpr3.cpp
new file mode 100644
index 0000000000000..dd52bb8f81d32
--- /dev/null
+++ b/clang/test/SemaCXX/microsoft-constexpr3.cpp
@@ -0,0 +1,18 @@
+// Ignore dynamic_cast when relaxing constant expression with -fms-compatibility
+// However using dynamic_cast is still possible in c++20 and higher
+// RUN: not %clang_cc1 -std=c++11 -fms-compatibility -fsyntax-only %s
+// RUN: %clang_cc1 -std=c++20 -fms-compatibility -fsyntax-only %s
+
+struct B {
+ virtual ~B() {}
+};
+
+struct D : B {
+ int x = 123;
+};
+
+#define IsD(x) (dynamic_cast<const D*>(x) != 0)
+
+static const D od;
+
+constexpr bool is_d = IsD(&od);
>From c66b4ad9bdcdab1bb80270fd293ce6c07a505415 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Thu, 14 May 2026 20:02:28 +0200
Subject: [PATCH 6/8] Improved diagnostics
---
.../include/clang/Basic/DiagnosticASTKinds.td | 5 ++--
clang/include/clang/Basic/PartialDiagnostic.h | 2 +-
clang/lib/AST/ASTContext.cpp | 30 +++++++++++++------
clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 ++-
4 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 192eb85b019cf..7f0f0a503a8ba 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -1031,10 +1031,11 @@ def warn_npot_ms_struct : Warning<
"data types with sizes that aren't a power of two">,
DefaultError, InGroup<IncompatibleMSStruct>;
def warn_relaxed_constant_fold : Extension<
- "folding this constant expression is a Microsoft extension">,
+ "folding constant expression involving "
+ "%select{reinterpret_cast|cast that performs the conversions of a reinterpret_cast}0"
+ " is a Microsoft extension">,
InGroup<MicrosoftRelaxedConstantFold>;
-
def err_itanium_layout_unimplemented : Error<
"Itanium-compatible layout for the Microsoft C++ ABI is not yet supported">;
diff --git a/clang/include/clang/Basic/PartialDiagnostic.h b/clang/include/clang/Basic/PartialDiagnostic.h
index 7658bfa3795f4..7469e45f7d888 100644
--- a/clang/include/clang/Basic/PartialDiagnostic.h
+++ b/clang/include/clang/Basic/PartialDiagnostic.h
@@ -194,7 +194,7 @@ class PartialDiagnostic : public StreamingDiagnostic {
assert(I < DiagStorage->NumDiagArgs && "Not enough diagnostic args");
assert(DiagStorage->DiagArgumentsKind[I] !=
DiagnosticsEngine::ak_std_string &&
- "Not an integer arg");
+ "Not a value arg");
return DiagStorage->DiagArgumentsVal[I];
}
};
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 6430bfa56159f..c779b72298895 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15584,14 +15584,26 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
bool ASTContext::maybeFoldMSConstexpr(
SmallVectorImpl<PartialDiagnosticAt> &Notes) {
- bool Fold =
- getLangOpts().MSVCCompat && Notes.size() == 1 &&
- Notes[0].second.getDiagID() == diag::note_constexpr_invalid_cast &&
- Notes[0].second.getValueArg(0) != diag::ConstexprInvalidCastKind::Dynamic;
-
- if (Fold) {
- getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold);
- Notes.clear();
+ if (Notes.size() != 1 || !getLangOpts().MSVCCompat)
+ return false;
+ auto &PD = Notes[0].second;
+ if (PD.getDiagID() != diag::note_constexpr_invalid_cast)
+ return false;
+ unsigned CastID;
+ switch (PD.getValueArg(0)) {
+ case diag::ConstexprInvalidCastKind::Reinterpret:
+ CastID = 0;
+ break;
+ case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret:
+ if (!PD.getValueArg(1))
+ return false;
+ CastID = 1;
+ break;
+ default:
+ return false;
}
- return Fold;
+ getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold)
+ << CastID;
+ Notes.clear();
+ return true;
}
diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp
index 25d4b3ed2b3f3..e756a0240d5fe 100644
--- a/clang/test/SemaCXX/microsoft-constexpr2.cpp
+++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp
@@ -3,10 +3,12 @@
typedef long long LONG_PTR;
typedef long LONG;
#define FIELD_OFFSET(type, field) ((LONG_PTR)&(((type *)0)->field))
+#define FIELD_OFFSET2(type, field) (reinterpret_cast<LONG_PTR>(&(((type *)0)->field)))
struct S {
int x;
int y;
};
-constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding this constant expression is a Microsoft extension}}
+constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}}
+constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}}
>From 15d7438b5764456904270f3f6866377b2f74ec79 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Mon, 18 May 2026 13:50:18 +0200
Subject: [PATCH 7/8] Add note to distinguish ptr to int conversions
---
.../include/clang/Basic/DiagnosticASTKinds.td | 4 ++++
clang/lib/AST/ASTContext.cpp | 18 +++---------------
clang/lib/AST/ExprConstant.cpp | 4 ++--
3 files changed, 9 insertions(+), 17 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 7f0f0a503a8ba..ce864bdb1fe8a 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -16,6 +16,10 @@ def note_constexpr_invalid_cast : Note<
"of a reinterpret_cast}1}|%CastFrom{cast from %1}}0"
" is not allowed in a constant expression"
"%select{| in C++ standards before C++20||}0">;
+def note_constexpr_invalid_cast_ptrtoint : Note<
+ "%select{reinterpret_cast||"
+ "%select{this conversion|cast that performs the conversions of a reinterpret_cast}1|"
+ "}0 is not allowed in a constant expression">;
def note_constexpr_invalid_void_star_cast : Note<
"cast from %0 is not allowed in a constant expression "
"%select{in C++ standards before C++2c|because the pointed object "
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index c779b72298895..89f3b3ffca83f 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15587,23 +15587,11 @@ bool ASTContext::maybeFoldMSConstexpr(
if (Notes.size() != 1 || !getLangOpts().MSVCCompat)
return false;
auto &PD = Notes[0].second;
- if (PD.getDiagID() != diag::note_constexpr_invalid_cast)
+ if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint)
return false;
- unsigned CastID;
- switch (PD.getValueArg(0)) {
- case diag::ConstexprInvalidCastKind::Reinterpret:
- CastID = 0;
- break;
- case diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret:
- if (!PD.getValueArg(1))
- return false;
- CastID = 1;
- break;
- default:
- return false;
- }
getDiagnostics().Report(Notes[0].first, diag::warn_relaxed_constant_fold)
- << CastID;
+ << !!PD.getValueArg(0);
Notes.clear();
+
return true;
}
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 3f3a80f5b77a3..1110b67ab15b2 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -8533,7 +8533,7 @@ class ExprEvaluatorBase
}
bool VisitCXXReinterpretCastExpr(const CXXReinterpretCastExpr *E) {
- CCEDiag(E, diag::note_constexpr_invalid_cast)
+ CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint)
<< diag::ConstexprInvalidCastKind::Reinterpret;
return static_cast<Derived*>(this)->VisitCastExpr(E);
}
@@ -19450,7 +19450,7 @@ bool IntExprEvaluator::VisitCastExpr(const CastExpr *E) {
}
case CK_PointerToIntegral: {
- CCEDiag(E, diag::note_constexpr_invalid_cast)
+ CCEDiag(E, diag::note_constexpr_invalid_cast_ptrtoint)
<< diag::ConstexprInvalidCastKind::ThisConversionOrReinterpret
<< Info.Ctx.getLangOpts().CPlusPlus << E->getSourceRange();
>From 129bcc59870caeac2aab88673b37ec4ef4c6a743 Mon Sep 17 00:00:00 2001
From: Evgeny Leviant <eleviant at accesssoftek.com>
Date: Tue, 19 May 2026 14:49:52 +0200
Subject: [PATCH 8/8] Don't allow LValue casts for constexpr
---
clang/include/clang/AST/ASTContext.h | 3 ++-
clang/lib/AST/ASTContext.cpp | 4 ++--
clang/lib/AST/Decl.cpp | 2 +-
clang/lib/Sema/SemaExpr.cpp | 2 +-
clang/lib/Sema/SemaOverload.cpp | 2 +-
clang/test/SemaCXX/microsoft-constexpr2.cpp | 4 +++-
6 files changed, 10 insertions(+), 7 deletions(-)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 951af8a604a85..b59cc97cdbfd4 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3873,7 +3873,8 @@ OPT_LIST(V)
void recordMemberDataPointerEvaluation(const ValueDecl *VD);
void recordOffsetOfEvaluation(const OffsetOfExpr *E);
- bool maybeFoldMSConstexpr(SmallVectorImpl<PartialDiagnosticAt> &Notes);
+ bool maybeFoldMSConstexpr(APValue &Val,
+ SmallVectorImpl<PartialDiagnosticAt> &Notes);
private:
/// All OMPTraitInfo objects live in this collection, one per
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 89f3b3ffca83f..82cc2fbb93dd3 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15583,8 +15583,8 @@ void ASTContext::recordOffsetOfEvaluation(const OffsetOfExpr *E) {
}
bool ASTContext::maybeFoldMSConstexpr(
- SmallVectorImpl<PartialDiagnosticAt> &Notes) {
- if (Notes.size() != 1 || !getLangOpts().MSVCCompat)
+ APValue &Val, SmallVectorImpl<PartialDiagnosticAt> &Notes) {
+ if (Notes.size() != 1 || !getLangOpts().MSVCCompat || Val.isLValue())
return false;
auto &PD = Notes[0].second;
if (PD.getDiagID() != diag::note_constexpr_invalid_cast_ptrtoint)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 1c23dbca7cd4e..9cadc893407d5 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2588,7 +2588,7 @@ APValue *VarDecl::evaluateValueImpl(SmallVectorImpl<PartialDiagnosticAt> &Notes,
(Ctx.getLangOpts().CPlusPlus ||
(isConstexpr() && Ctx.getLangOpts().C23)) &&
!Notes.empty()) {
- if (!Ctx.maybeFoldMSConstexpr(Notes))
+ if (!Ctx.maybeFoldMSConstexpr(Eval->Evaluated, Notes))
Result = false;
}
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index e4da73a54d32e..b77777e9f02ce 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -18023,7 +18023,7 @@ Sema::VerifyIntegerConstantExpression(Expr *E, llvm::APSInt *Result,
// For -fms-compatibility mode we relax some requirements
// for constant folding in non-SFINAE contexts
if (!isSFINAEContext())
- getASTContext().maybeFoldMSConstexpr(Notes);
+ getASTContext().maybeFoldMSConstexpr(EvalResult.Val, Notes);
// In C++11, we can rely on diagnostics being produced for any expression
// which is not a constant expression. If no diagnostics were produced, then
diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp
index e80b17ab6634f..8d533caac676c 100644
--- a/clang/lib/Sema/SemaOverload.cpp
+++ b/clang/lib/Sema/SemaOverload.cpp
@@ -6704,7 +6704,7 @@ Sema::EvaluateConvertedConstantExpression(Expr *E, QualType T, APValue &Value,
// For -fms-compatibility mode we relax some requirements
// for constant folding in non-SFINAE contexts
if (!isSFINAEContext())
- getASTContext().maybeFoldMSConstexpr(Notes);
+ getASTContext().maybeFoldMSConstexpr(Value, Notes);
if (Notes.empty()) {
// It's a constant expression.
Expr *E = Result.get();
diff --git a/clang/test/SemaCXX/microsoft-constexpr2.cpp b/clang/test/SemaCXX/microsoft-constexpr2.cpp
index e756a0240d5fe..ad4faddc83b1d 100644
--- a/clang/test/SemaCXX/microsoft-constexpr2.cpp
+++ b/clang/test/SemaCXX/microsoft-constexpr2.cpp
@@ -8,7 +8,9 @@ typedef long LONG;
struct S {
int x;
int y;
-};
+} ob;
constexpr long b = FIELD_OFFSET(S, y); // expected-warning {{folding constant expression involving cast that performs the conversions of a reinterpret_cast is a Microsoft extension}}
constexpr long b2 = FIELD_OFFSET2(S, y); // expected-warning {{folding constant expression involving reinterpret_cast is a Microsoft extension}}
+constexpr LONG_PTR b3 = (LONG_PTR)&ob; // expected-error {{constexpr variable 'b3' must be initialized by a constant expression}}
+ // expected-note 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