[clang] [Clang] add support for C23 'H', 'D', and 'DD' length modifiers (PR #201098)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Jun 2 04:35:07 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Oleksandr Tarasiuk (a-tarasyuk)
<details>
<summary>Changes</summary>
This patch adds `-Wformat` support for the C23 `H`, `D`, and `DD` length modifiers in `printf`/`scanf` format strings. #<!-- -->116962
---
Full diff: https://github.com/llvm/llvm-project/pull/201098.diff
6 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+1)
- (modified) clang/include/clang/AST/FormatString.h (+4)
- (modified) clang/lib/AST/FormatString.cpp (+45)
- (modified) clang/lib/AST/PrintfFormatString.cpp (+20-2)
- (modified) clang/lib/AST/ScanfFormatString.cpp (+15)
- (added) clang/test/Sema/format-strings-decimal.c (+57)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index cef93e25f1e7d..ecaffff1e03f2 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -227,6 +227,7 @@ C2y Feature Support
C23 Feature Support
^^^^^^^^^^^^^^^^^^^
- Clang now allows C23 ``constexpr`` struct member access through the dot operator in constant expressions. (#GH178349)
+- Clang now supports the C23 ``H``, ``D``, and ``DD`` length modifiers. (#GH116962)
Objective-C Language Changes
-----------------------------
diff --git a/clang/include/clang/AST/FormatString.h b/clang/include/clang/AST/FormatString.h
index a3382e1a1d007..f7f99fcbc08cc 100644
--- a/clang/include/clang/AST/FormatString.h
+++ b/clang/include/clang/AST/FormatString.h
@@ -80,6 +80,9 @@ class LengthModifier {
AsInt3264, // 'I' (MSVCRT, like __int3264 from MIDL)
AsInt64, // 'I64' (MSVCRT, like __int64)
AsLongDouble, // 'L'
+ AsDecimal32, // 'H' (C23, _Decimal32)
+ AsDecimal64, // 'D' (C23, _Decimal64)
+ AsDecimal128, // 'DD' (C23, _Decimal128)
AsAllocate, // for '%as', GNU extension to C90 scanf
AsMAllocate, // for '%ms', GNU extension to scanf
AsWide, // 'w' (MSVCRT, like l but only for c, C, s, S, or Z
@@ -97,6 +100,7 @@ class LengthModifier {
return 1;
case AsLongLong:
case AsChar:
+ case AsDecimal128:
return 2;
case AsInt32:
case AsInt64:
diff --git a/clang/lib/AST/FormatString.cpp b/clang/lib/AST/FormatString.cpp
index 7e1ac0de6dcaf..7c327ab20f4b6 100644
--- a/clang/lib/AST/FormatString.cpp
+++ b/clang/lib/AST/FormatString.cpp
@@ -286,6 +286,25 @@ bool clang::analyze_format_string::ParseLengthModifier(FormatSpecifier &FS,
++I;
lmKind = LengthModifier::AsInt3264;
break;
+ case 'H':
+ if (LO.C23) {
+ lmKind = LengthModifier::AsDecimal32;
+ ++I;
+ break;
+ }
+ return false;
+ case 'D':
+ if (LO.C23) {
+ ++I;
+ if (I != E && *I == 'D') {
+ ++I;
+ lmKind = LengthModifier::AsDecimal128;
+ } else {
+ lmKind = LengthModifier::AsDecimal64;
+ }
+ break;
+ }
+ return false;
case 'w':
lmKind = LengthModifier::AsWide;
++I;
@@ -875,6 +894,12 @@ const char *analyze_format_string::LengthModifier::toString() const {
return "I64";
case AsLongDouble:
return "L";
+ case AsDecimal32:
+ return "H";
+ case AsDecimal64:
+ return "D";
+ case AsDecimal128:
+ return "DD";
case AsAllocate:
return "a";
case AsMAllocate:
@@ -1202,6 +1227,23 @@ bool FormatSpecifier::hasValidLengthModifier(const TargetInfo &Target,
default:
return false;
}
+
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
+ switch (CS.getKind()) {
+ case ConversionSpecifier::aArg:
+ case ConversionSpecifier::AArg:
+ case ConversionSpecifier::eArg:
+ case ConversionSpecifier::EArg:
+ case ConversionSpecifier::fArg:
+ case ConversionSpecifier::FArg:
+ case ConversionSpecifier::gArg:
+ case ConversionSpecifier::GArg:
+ return LO.C23;
+ default:
+ return false;
+ }
}
llvm_unreachable("Invalid LengthModifier Kind!");
}
@@ -1217,6 +1259,9 @@ bool FormatSpecifier::hasStandardLengthModifier() const {
case LengthModifier::AsSizeT:
case LengthModifier::AsPtrDiff:
case LengthModifier::AsLongDouble:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return true;
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
diff --git a/clang/lib/AST/PrintfFormatString.cpp b/clang/lib/AST/PrintfFormatString.cpp
index 6610a2de9e083..e39c27cdf1b63 100644
--- a/clang/lib/AST/PrintfFormatString.cpp
+++ b/clang/lib/AST/PrintfFormatString.cpp
@@ -604,6 +604,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
}
@@ -642,6 +645,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsAllocate:
case LengthModifier::AsMAllocate:
case LengthModifier::AsWide:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
}
@@ -658,9 +664,18 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
}
}
- if (LM.getKind() == LengthModifier::AsLongDouble)
+ switch (LM.getKind()) {
+ case LengthModifier::AsLongDouble:
return Ctx.LongDoubleTy;
- return Ctx.DoubleTy;
+ case LengthModifier::AsDecimal32:
+ return ArgType(Ctx.DoubleTy, "_Decimal32");
+ case LengthModifier::AsDecimal64:
+ return ArgType(Ctx.DoubleTy, "_Decimal64");
+ case LengthModifier::AsDecimal128:
+ return ArgType(Ctx.LongDoubleTy, "_Decimal128");
+ default:
+ return Ctx.DoubleTy;
+ }
}
if (CS.getKind() == ConversionSpecifier::nArg) {
@@ -692,6 +707,9 @@ ArgType PrintfSpecifier::getScalarArgType(ASTContext &Ctx,
case LengthModifier::AsInt3264:
case LengthModifier::AsInt64:
case LengthModifier::AsWide:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
case LengthModifier::AsShortLong:
llvm_unreachable("only used for OpenCL which doesn not handle nArg");
diff --git a/clang/lib/AST/ScanfFormatString.cpp b/clang/lib/AST/ScanfFormatString.cpp
index 90cbbd60bbcf5..8e4300820db74 100644
--- a/clang/lib/AST/ScanfFormatString.cpp
+++ b/clang/lib/AST/ScanfFormatString.cpp
@@ -309,6 +309,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsInt3264:
case LengthModifier::AsWide:
case LengthModifier::AsShortLong:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
}
llvm_unreachable("Unsupported LengthModifier Type");
@@ -353,6 +356,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsInt3264:
case LengthModifier::AsWide:
case LengthModifier::AsShortLong:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
}
llvm_unreachable("Unsupported LengthModifier Type");
@@ -373,6 +379,12 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
return ArgType::PtrTo(Ctx.DoubleTy);
case LengthModifier::AsLongDouble:
return ArgType::PtrTo(Ctx.LongDoubleTy);
+ case LengthModifier::AsDecimal32:
+ return ArgType::PtrTo(ArgType(Ctx.FloatTy, "_Decimal32"));
+ case LengthModifier::AsDecimal64:
+ return ArgType::PtrTo(ArgType(Ctx.DoubleTy, "_Decimal64"));
+ case LengthModifier::AsDecimal128:
+ return ArgType::PtrTo(ArgType(Ctx.LongDoubleTy, "_Decimal128"));
default:
return ArgType::Invalid();
}
@@ -451,6 +463,9 @@ ArgType ScanfSpecifier::getArgType(ASTContext &Ctx) const {
case LengthModifier::AsInt3264:
case LengthModifier::AsWide:
case LengthModifier::AsShortLong:
+ case LengthModifier::AsDecimal32:
+ case LengthModifier::AsDecimal64:
+ case LengthModifier::AsDecimal128:
return ArgType::Invalid();
}
diff --git a/clang/test/Sema/format-strings-decimal.c b/clang/test/Sema/format-strings-decimal.c
new file mode 100644
index 0000000000000..abb02255c7339
--- /dev/null
+++ b/clang/test/Sema/format-strings-decimal.c
@@ -0,0 +1,57 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -fsyntax-only -verify -Wformat %s
+
+int printf(const char *restrict, ...);
+int scanf(const char *restrict, ...);
+
+void t1(float f, double d, long double ld) {
+ printf("%Hf", f);
+ printf("%He", f);
+ printf("%Hg", f);
+ printf("%Ha", f);
+ printf("%HF", f);
+ printf("%HE", f);
+ printf("%HG", f);
+ printf("%HA", f);
+
+ printf("%Df", d);
+ printf("%De", d);
+ printf("%Dg", d);
+ printf("%Da", d);
+ printf("%DF", d);
+ printf("%DE", d);
+ printf("%DG", d);
+ printf("%DA", d);
+
+ printf("%DDf", ld);
+ printf("%DDe", ld);
+ printf("%DDg", ld);
+ printf("%DDa", ld);
+ printf("%DDF", ld);
+ printf("%DDE", ld);
+ printf("%DDG", ld);
+ printf("%DDA", ld);
+}
+
+void t2(int i, float f, double d) {
+ printf("%Df", i); // expected-warning{{format specifies type '_Decimal64'}}
+ printf("%Hf", i); // expected-warning{{format specifies type '_Decimal32'}}
+ printf("%DDf", i); // expected-warning{{format specifies type '_Decimal128'}}
+}
+
+void t3(float f) {
+ printf("%Hd", f); // expected-warning{{length modifier 'H' results in undefined behavior or no effect with 'd' conversion specifier}}
+ printf("%Dd", f); // expected-warning{{length modifier 'D' results in undefined behavior or no effect with 'd' conversion specifier}}
+ printf("%DDd", f); // expected-warning{{length modifier 'DD' results in undefined behavior or no effect with 'd' conversion specifier}}
+ printf("%Hs", "str"); // expected-warning{{length modifier 'H' results in undefined behavior or no effect with 's' conversion specifier}}
+}
+
+void t4(double *d_ptr, float *f_ptr, long double *ld_ptr) {
+ scanf("%Hf", f_ptr);
+ scanf("%Df", d_ptr);
+ scanf("%DDf", ld_ptr);
+}
+
+void t5(int *i_ptr) {
+ scanf("%Df", i_ptr); // expected-warning{{format specifies type '_Decimal64 *'}}
+ scanf("%Hf", i_ptr); // expected-warning{{format specifies type '_Decimal32 *'}}
+}
``````````
</details>
https://github.com/llvm/llvm-project/pull/201098
More information about the cfe-commits
mailing list