Skip to content

[C2y] Add octal prefixes, deprecate unprefixed octals #131626

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Mar 18, 2025

Conversation

AaronBallman
Copy link
Collaborator

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.

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.
@AaronBallman AaronBallman added clang:frontend Language frontend issues, e.g. anything involving "Sema" extension:clang c2y labels Mar 17, 2025
@llvmbot llvmbot added the clang Clang issues not falling into any other category label Mar 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 17, 2025

@llvm/pr-subscribers-clang

Author: Aaron Ballman (AaronBallman)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/131626.diff

7 Files Affected:

  • (modified) clang/docs/LanguageExtensions.rst (+1)
  • (modified) clang/docs/ReleaseNotes.rst (+4)
  • (modified) clang/include/clang/Basic/DiagnosticGroups.td (+3-1)
  • (modified) clang/include/clang/Basic/DiagnosticLexKinds.td (+8)
  • (modified) clang/lib/Lex/LiteralSupport.cpp (+22)
  • (added) clang/test/C/C2y/n3353.c (+95)
  • (modified) clang/www/c_status.html (+1-1)
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>

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on that Aaron

Copy link

github-actions bot commented Mar 17, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Comment on lines +585 to +590
/// Diagnose use of a delimited or named escape sequence.
static void DiagnoseDelimitedOrNamedEscapeSequence(SourceLocation Loc,
bool Named,
const LangOptions &Opts,
DiagnosticsEngine &Diags);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be a non-member static function Lexer.cpp

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called from LiteralSupport.cpp, so it could be a non-member function, but I think it's a bit cleaner to scope it to the Lexer to make it clear this is emitting lexer-based diagnostics. WDYT?

Copy link
Contributor

@cor3ntin cor3ntin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Aaron!

@AaronBallman AaronBallman merged commit 9cf46fb into llvm:main Mar 18, 2025
12 checks passed
@AaronBallman AaronBallman deleted the aballman-wg14-n3353 branch March 18, 2025 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c2y clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category extension:clang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants