[clang] [C2y] Add octal prefixes, deprecate unprefixed octals (PR #131626)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Mon Mar 17 08:16:09 PDT 2025
https://github.com/AaronBallman created https://github.com/llvm/llvm-project/pull/131626
WG14 N3353 added support for 0o and 0O as octal literal prefixes. It also deprecates use of octal literals without a prefix, except for the literal 0.
This feature is being exposed as an extension in older C language modes as well as in all C++ language modes.
>From f3c0bc1d271495372b56716e87f033e43aa022b3 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Mon, 17 Mar 2025 11:12:56 -0400
Subject: [PATCH] [C2y] Add octal prefixes, deprecate unprefixed octals
WG14 N3353 added support for 0o and 0O as octal literal prefixes. It
also deprecates use of octal literals without a prefix, except for the
literal 0.
This feature is being exposed as an extension in older C language modes
as well as in all C++ language modes.
---
clang/docs/LanguageExtensions.rst | 1 +
clang/docs/ReleaseNotes.rst | 4 +
clang/include/clang/Basic/DiagnosticGroups.td | 4 +-
.../include/clang/Basic/DiagnosticLexKinds.td | 8 ++
clang/lib/Lex/LiteralSupport.cpp | 22 +++++
clang/test/C/C2y/n3353.c | 95 +++++++++++++++++++
clang/www/c_status.html | 2 +-
7 files changed, 134 insertions(+), 2 deletions(-)
create mode 100644 clang/test/C/C2y/n3353.c
diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst
index cc12ff5bad353..a96816916b5f2 100644
--- a/clang/docs/LanguageExtensions.rst
+++ b/clang/docs/LanguageExtensions.rst
@@ -1652,6 +1652,7 @@ Designated initializers (N494) C
Array & element qualification (N2607) C23 C89
Attributes (N2335) C23 C89
``#embed`` (N3017) C23 C89, C++
+Octal literals prefixed with ``0o`` or ``0O`` C2y C89, C++
============================================= ================================ ============= =============
Builtin type aliases
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 2a1c5ee2d788e..bfb6235247709 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -120,6 +120,10 @@ C2y Feature Support
- Implemented `WG14 N3411 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3411.pdf>`_
which allows a source file to not end with a newline character. This is still
reported as a conforming extension in earlier language modes.
+- Implemented `WG14 N3353 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm>_`
+ which adds the new ``0o`` and ``0O`` ocal literal prefixes and deprecates
+ octal literals other than ``0`` which do not start with the new prefix. This
+ feature is exposed in earlier language modes and in C++ as an extension.
C23 Feature Support
^^^^^^^^^^^^^^^^^^^
diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td
index fac80fb4009aa..e386d8f2c3b27 100644
--- a/clang/include/clang/Basic/DiagnosticGroups.td
+++ b/clang/include/clang/Basic/DiagnosticGroups.td
@@ -92,6 +92,7 @@ def EnumCompare : DiagGroup<"enum-compare", [EnumCompareSwitch,
def DeprecatedAnonEnumEnumConversion : DiagGroup<"deprecated-anon-enum-enum-conversion">;
def DeprecatedEnumEnumConversion : DiagGroup<"deprecated-enum-enum-conversion">;
def DeprecatedEnumFloatConversion : DiagGroup<"deprecated-enum-float-conversion">;
+def DeprecatedOctalLiterals : DiagGroup<"deprecated-octal-literals">;
def AnonEnumEnumConversion : DiagGroup<"anon-enum-enum-conversion",
[DeprecatedAnonEnumEnumConversion]>;
def EnumEnumConversion : DiagGroup<"enum-enum-conversion",
@@ -235,7 +236,8 @@ def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion,
DeprecatedVolatile,
DeprecatedWritableStr,
DeprecatedRedundantConstexprStaticDef,
- DeprecatedMissingCommaVariadicParam
+ DeprecatedMissingCommaVariadicParam,
+ DeprecatedOctalLiterals
]>,
DiagCategory<"Deprecations">;
diff --git a/clang/include/clang/Basic/DiagnosticLexKinds.td b/clang/include/clang/Basic/DiagnosticLexKinds.td
index 0e5592d65669b..f662ad1211ba3 100644
--- a/clang/include/clang/Basic/DiagnosticLexKinds.td
+++ b/clang/include/clang/Basic/DiagnosticLexKinds.td
@@ -256,6 +256,14 @@ def warn_cxx17_hex_literal : Warning<
"hexadecimal floating literals are incompatible with "
"C++ standards before C++17">,
InGroup<CXXPre17CompatPedantic>, DefaultIgnore;
+def ext_octal_literal : Extension<
+ "octal integer literals are a C2y extension">, InGroup<C2y>;
+def warn_c2y_compat_octal_literal : Warning<
+ "octal integer literals are incompatible with standards before C2y">,
+ InGroup<CPre2yCompat>, DefaultIgnore;
+def warn_unprefixed_octal_deprecated : Warning<
+ "octal literals without a '0o' prefix are deprecated">,
+ InGroup<DeprecatedOctalLiterals>;
def ext_binary_literal : Extension<
"binary integer literals are a C23 extension">, InGroup<C23>;
def warn_c23_compat_binary_literal : Warning<
diff --git a/clang/lib/Lex/LiteralSupport.cpp b/clang/lib/Lex/LiteralSupport.cpp
index 69dc057d0df4b..0ab27b2d40373 100644
--- a/clang/lib/Lex/LiteralSupport.cpp
+++ b/clang/lib/Lex/LiteralSupport.cpp
@@ -21,6 +21,7 @@
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/Token.h"
#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringSwitch.h"
@@ -1423,6 +1424,27 @@ void NumericLiteralParser::ParseNumberStartingWithZero(SourceLocation TokLoc) {
return;
}
+ // Parse a potential octal literal prefix.
+ bool SawOctalPrefix = false;
+ if ((c1 == 'O' || c1 == 'o') && (s[1] >= '0' && s[1] <= '7')) {
+ unsigned DiagId;
+ if (LangOpts.C2y)
+ DiagId = diag::warn_c2y_compat_octal_literal;
+ else
+ DiagId = diag::ext_octal_literal;
+ Diags.Report(TokLoc, DiagId);
+ ++s;
+ DigitsBegin = s;
+ SawOctalPrefix = true;
+ }
+
+ auto _ = llvm::make_scope_exit([&] {
+ // If we still have an octal value but we did not see an octal prefix,
+ // diagnose as being an obsolescent feature starting in C2y.
+ if (radix == 8 && LangOpts.C2y && !SawOctalPrefix && !hadError)
+ Diags.Report(TokLoc, diag::warn_unprefixed_octal_deprecated);
+ });
+
// For now, the radix is set to 8. If we discover that we have a
// floating point constant, the radix will change to 10. Octal floating
// point constants are not permitted (only decimal and hexadecimal).
diff --git a/clang/test/C/C2y/n3353.c b/clang/test/C/C2y/n3353.c
new file mode 100644
index 0000000000000..4e8b7f0fc43ba
--- /dev/null
+++ b/clang/test/C/C2y/n3353.c
@@ -0,0 +1,95 @@
+// RUN: %clang_cc1 -verify=expected,c2y -pedantic -std=c2y %s
+// RUN: %clang_cc1 -verify=expected,c2y,compat -Wpre-c2y-compat -std=c2y %s
+// RUN: %clang_cc1 -verify=expected,ext -pedantic -std=c23 %s
+// RUN: %clang_cc1 -verify=expected,ext -pedantic -x c++ -Wno-c11-extensions %s
+
+
+/* WG14 N3353: Clang 21
+ * Obsolete implicitly octal literals and add delimited escape sequences
+ */
+
+constexpr int i = 0234; // c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+constexpr int j = 0o234; /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+
+static_assert(i == 156);
+static_assert(j == 156);
+
+// Show that 0O is the same as Oo (tested above)
+static_assert(0O1234 == 0o1234); /* ext-warning 2 {{octal integer literals are a C2y extension}}
+ compat-warning 2 {{octal integer literals are incompatible with standards before C2y}}
+ */
+
+// Demonstrate that it works fine in the preprocessor.
+#if 0o123 != 0x53 /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+#error "oh no, math stopped working!"
+#endif
+
+// 0 by itself is not deprecated, of course.
+int k = 0;
+
+// Make sure there are no surprises with auto and type deduction. Promotion
+// turns this into an 'int', and 'constexpr' implies 'const'.
+constexpr auto l = 0o1234567; /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+static_assert(l == 0x53977);
+static_assert(__extension__ _Generic(typeof(0o1), typeof(01) : 1, default : 0)); /* c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+ compat-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+static_assert(__extension__ _Generic(typeof(l), const int : 1, default : 0)); // compat-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}}
+
+// Note that 0o by itself is an invalid literal.
+int m = 0o; /* expected-error {{invalid suffix 'o' on integer constant}}
+ c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+ */
+
+// Ensure negation works as expected.
+static_assert(-0o1234 == -668); /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+
+// FIXME: it would be better to not diagnose the compat and ext warnings when
+// the octal literal is invalid.
+// We expect diagnostics for non-octal digits.
+int n = 0o18; /* expected-error {{invalid digit '8' in octal constant}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ ext-warning {{octal integer literals are a C2y extension}}
+ */
+int o1 = 0o8; /* expected-error {{invalid suffix 'o8' on integer constant}}
+ c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+ */
+// FIXME: however, it matches the behavior for hex literals in terms of the
+// error reported. Unfortunately, we then go on to think 0 is an octal literal
+// without a prefix, which is again a bit confusing.
+int o2 = 0xG; /* expected-error {{invalid suffix 'xG' on integer constant}}
+ c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+ */
+
+// Ensure digit separators work as expected.
+constexpr int p = 0o0'1'2'3'4'5'6'7; /* compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ ext-warning {{octal integer literals are a C2y extension}}
+ */
+static_assert(p == 01234567); // c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+int q = 0o'0'1; /* expected-error {{invalid suffix 'o'0'1' on integer constant}}
+ c2y-warning {{octal literals without a '0o' prefix are deprecated}}
+ */
+
+#ifdef __cplusplus
+template <unsigned N>
+struct S {
+ static_assert(N == 0o567); /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+};
+
+void foo() {
+ S<0o567> s; /* ext-warning {{octal integer literals are a C2y extension}}
+ compat-warning {{octal integer literals are incompatible with standards before C2y}}
+ */
+}
+#endif
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index d68e8d6441ed2..7cf50bfdb6639 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -176,7 +176,7 @@ <h2 id="c2y">C2y implementation status</h2>
<tr>
<td>Obsolete implicitly octal literals and add delimited escape sequences</td>
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm">N3353</a></td>
- <td class="none" align="center">No</td>
+ <td class="unreleased" align="center">Clang 21</td>
</tr>
<tr>
<td>'if' declarations, v2</td>
More information about the cfe-commits
mailing list