Skip to content

Commit 9cf46fb

Browse files
authored
[C2y] Add octal prefixes, deprecate unprefixed octals (#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.
1 parent 31e98c7 commit 9cf46fb

File tree

9 files changed

+229
-28
lines changed

9 files changed

+229
-28
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,7 @@ Designated initializers (N494) C
16521652
Array & element qualification (N2607) C23 C89
16531653
Attributes (N2335) C23 C89
16541654
``#embed`` (N3017) C23 C89, C++
1655+
Octal literals prefixed with ``0o`` or ``0O`` C2y C89, C++
16551656
============================================= ================================ ============= =============
16561657

16571658
Builtin type aliases

clang/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,13 @@ C2y Feature Support
129129
- Implemented `WG14 N3411 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3411.pdf>`_
130130
which allows a source file to not end with a newline character. This is still
131131
reported as a conforming extension in earlier language modes.
132+
- Implemented `WG14 N3353 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm>_`
133+
which adds the new ``0o`` and ``0O`` ocal literal prefixes and deprecates
134+
octal literals other than ``0`` which do not start with the new prefix. This
135+
feature is exposed in earlier language modes and in C++ as an extension. The
136+
paper also introduced octal and hexadecimal delimited escape sequences (e.g.,
137+
``"\x{12}\o{12}"``) which are also supported as an extension in older C
138+
language modes.
132139

133140
C23 Feature Support
134141
^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ def EnumCompare : DiagGroup<"enum-compare", [EnumCompareSwitch,
9292
def DeprecatedAnonEnumEnumConversion : DiagGroup<"deprecated-anon-enum-enum-conversion">;
9393
def DeprecatedEnumEnumConversion : DiagGroup<"deprecated-enum-enum-conversion">;
9494
def DeprecatedEnumFloatConversion : DiagGroup<"deprecated-enum-float-conversion">;
95+
def DeprecatedOctalLiterals : DiagGroup<"deprecated-octal-literals">;
9596
def AnonEnumEnumConversion : DiagGroup<"anon-enum-enum-conversion",
9697
[DeprecatedAnonEnumEnumConversion]>;
9798
def EnumEnumConversion : DiagGroup<"enum-enum-conversion",
@@ -235,7 +236,8 @@ def Deprecated : DiagGroup<"deprecated", [DeprecatedAnonEnumEnumConversion,
235236
DeprecatedVolatile,
236237
DeprecatedWritableStr,
237238
DeprecatedRedundantConstexprStaticDef,
238-
DeprecatedMissingCommaVariadicParam
239+
DeprecatedMissingCommaVariadicParam,
240+
DeprecatedOctalLiterals
239241
]>,
240242
DiagCategory<"Deprecations">;
241243

clang/include/clang/Basic/DiagnosticLexKinds.td

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -148,14 +148,14 @@ def ext_mathematical_notation : ExtWarn<
148148
InGroup<DiagGroup<"mathematical-notation-identifier-extension">>;
149149

150150
def ext_delimited_escape_sequence : Extension<
151-
"%select{delimited|named}0 escape sequences are a "
152-
"%select{Clang|C++23}1 extension">,
153-
InGroup<DiagGroup<"delimited-escape-sequence-extension">>;
154-
151+
"%select{delimited|named}0 escape sequences are a %select{C++23|C2y|Clang}1 "
152+
"extension">, InGroup<DiagGroup<"delimited-escape-sequence-extension">>;
155153
def warn_cxx23_delimited_escape_sequence : Warning<
156-
"%select{delimited|named}0 escape sequences are "
157-
"incompatible with C++ standards before C++23">,
158-
InGroup<CXXPre23Compat>, DefaultIgnore;
154+
"%select{delimited|named}0 escape sequences are incompatible with C++ "
155+
"standards before C++23">, InGroup<CXXPre23Compat>, DefaultIgnore;
156+
def warn_c2y_delimited_escape_sequence : Warning<
157+
"delimited escape sequences are incompatible with C standards before C2y">,
158+
InGroup<CPre2yCompat>, DefaultIgnore;
159159

160160
def err_delimited_escape_empty : Error<
161161
"delimited escape sequence cannot be empty">;
@@ -256,6 +256,17 @@ def warn_cxx17_hex_literal : Warning<
256256
"hexadecimal floating literals are incompatible with "
257257
"C++ standards before C++17">,
258258
InGroup<CXXPre17CompatPedantic>, DefaultIgnore;
259+
def ext_octal_literal : Extension<
260+
"octal integer literals are a C2y extension">, InGroup<C2y>;
261+
def ext_cpp_octal_literal : Extension<
262+
"octal integer literals are a Clang extension">,
263+
InGroup<DiagGroup<"octal-prefix-extension">>;
264+
def warn_c2y_compat_octal_literal : Warning<
265+
"octal integer literals are incompatible with standards before C2y">,
266+
InGroup<CPre2yCompat>, DefaultIgnore;
267+
def warn_unprefixed_octal_deprecated : Warning<
268+
"octal literals without a '0o' prefix are deprecated">,
269+
InGroup<DeprecatedOctalLiterals>;
259270
def ext_binary_literal : Extension<
260271
"binary integer literals are a C23 extension">, InGroup<C23>;
261272
def warn_c23_compat_binary_literal : Warning<

clang/include/clang/Lex/Lexer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,12 @@ class Lexer : public PreprocessorLexer {
582582
/// sequence.
583583
static bool isNewLineEscaped(const char *BufferStart, const char *Str);
584584

585+
/// Diagnose use of a delimited or named escape sequence.
586+
static void DiagnoseDelimitedOrNamedEscapeSequence(SourceLocation Loc,
587+
bool Named,
588+
const LangOptions &Opts,
589+
DiagnosticsEngine &Diags);
590+
585591
/// Represents a char and the number of bytes parsed to produce it.
586592
struct SizedChar {
587593
char Char;

clang/lib/Lex/Lexer.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3405,6 +3405,30 @@ bool Lexer::isCodeCompletionPoint(const char *CurPtr) const {
34053405
return false;
34063406
}
34073407

3408+
void Lexer::DiagnoseDelimitedOrNamedEscapeSequence(SourceLocation Loc,
3409+
bool Named,
3410+
const LangOptions &Opts,
3411+
DiagnosticsEngine &Diags) {
3412+
unsigned DiagId;
3413+
if (Opts.CPlusPlus23)
3414+
DiagId = diag::warn_cxx23_delimited_escape_sequence;
3415+
else if (Opts.C2y && !Named)
3416+
DiagId = diag::warn_c2y_delimited_escape_sequence;
3417+
else
3418+
DiagId = diag::ext_delimited_escape_sequence;
3419+
3420+
// The trailing arguments are only used by the extension warning; either this
3421+
// is a C2y extension or a C++23 extension, unless it's a named escape
3422+
// sequence in C, then it's a Clang extension.
3423+
unsigned Ext;
3424+
if (!Opts.CPlusPlus)
3425+
Ext = Named ? 2 /* Clang extension */ : 1 /* C2y extension */;
3426+
else
3427+
Ext = 0; // C++23 extension
3428+
3429+
Diags.Report(Loc, DiagId) << Named << Ext;
3430+
}
3431+
34083432
std::optional<uint32_t> Lexer::tryReadNumericUCN(const char *&StartPtr,
34093433
const char *SlashLoc,
34103434
Token *Result) {
@@ -3496,12 +3520,10 @@ std::optional<uint32_t> Lexer::tryReadNumericUCN(const char *&StartPtr,
34963520
return std::nullopt;
34973521
}
34983522

3499-
if (Delimited && PP) {
3500-
Diag(SlashLoc, PP->getLangOpts().CPlusPlus23
3501-
? diag::warn_cxx23_delimited_escape_sequence
3502-
: diag::ext_delimited_escape_sequence)
3503-
<< /*delimited*/ 0 << (PP->getLangOpts().CPlusPlus ? 1 : 0);
3504-
}
3523+
if (Delimited && PP)
3524+
DiagnoseDelimitedOrNamedEscapeSequence(getSourceLocation(SlashLoc), false,
3525+
PP->getLangOpts(),
3526+
PP->getDiagnostics());
35053527

35063528
if (Result) {
35073529
Result->setFlag(Token::HasUCN);
@@ -3585,10 +3607,9 @@ std::optional<uint32_t> Lexer::tryReadNamedUCN(const char *&StartPtr,
35853607
}
35863608

35873609
if (Diagnose && Match)
3588-
Diag(SlashLoc, PP->getLangOpts().CPlusPlus23
3589-
? diag::warn_cxx23_delimited_escape_sequence
3590-
: diag::ext_delimited_escape_sequence)
3591-
<< /*named*/ 1 << (PP->getLangOpts().CPlusPlus ? 1 : 0);
3610+
DiagnoseDelimitedOrNamedEscapeSequence(getSourceLocation(SlashLoc), true,
3611+
PP->getLangOpts(),
3612+
PP->getDiagnostics());
35923613

35933614
// If no diagnostic has been emitted yet, likely because we are doing a
35943615
// tentative lexing, we do not want to recover here to make sure the token

clang/lib/Lex/LiteralSupport.cpp

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "clang/Lex/Preprocessor.h"
2222
#include "clang/Lex/Token.h"
2323
#include "llvm/ADT/APInt.h"
24+
#include "llvm/ADT/ScopeExit.h"
2425
#include "llvm/ADT/SmallVector.h"
2526
#include "llvm/ADT/StringExtras.h"
2627
#include "llvm/ADT/StringSwitch.h"
@@ -353,10 +354,8 @@ static unsigned ProcessCharEscape(const char *ThisTokBegin,
353354
diag::err_expected)
354355
<< tok::r_brace;
355356
else if (!HadError) {
356-
Diag(Diags, Features, Loc, ThisTokBegin, EscapeBegin, ThisTokBuf,
357-
Features.CPlusPlus23 ? diag::warn_cxx23_delimited_escape_sequence
358-
: diag::ext_delimited_escape_sequence)
359-
<< /*delimited*/ 0 << (Features.CPlusPlus ? 1 : 0);
357+
Lexer::DiagnoseDelimitedOrNamedEscapeSequence(Loc, false, Features,
358+
*Diags);
360359
}
361360
}
362361

@@ -709,11 +708,8 @@ static bool ProcessUCNEscape(const char *ThisTokBegin, const char *&ThisTokBuf,
709708
diag::warn_ucn_not_valid_in_c89_literal);
710709

711710
if ((IsDelimitedEscapeSequence || IsNamedEscapeSequence) && Diags)
712-
Diag(Diags, Features, Loc, ThisTokBegin, UcnBegin, ThisTokBuf,
713-
Features.CPlusPlus23 ? diag::warn_cxx23_delimited_escape_sequence
714-
: diag::ext_delimited_escape_sequence)
715-
<< (IsNamedEscapeSequence ? 1 : 0) << (Features.CPlusPlus ? 1 : 0);
716-
711+
Lexer::DiagnoseDelimitedOrNamedEscapeSequence(Loc, IsNamedEscapeSequence,
712+
Features, *Diags);
717713
return true;
718714
}
719715

@@ -1423,6 +1419,29 @@ void NumericLiteralParser::ParseNumberStartingWithZero(SourceLocation TokLoc) {
14231419
return;
14241420
}
14251421

1422+
// Parse a potential octal literal prefix.
1423+
bool SawOctalPrefix = false;
1424+
if ((c1 == 'O' || c1 == 'o') && (s[1] >= '0' && s[1] <= '7')) {
1425+
unsigned DiagId;
1426+
if (LangOpts.C2y)
1427+
DiagId = diag::warn_c2y_compat_octal_literal;
1428+
else if (LangOpts.CPlusPlus)
1429+
DiagId = diag::ext_cpp_octal_literal;
1430+
else
1431+
DiagId = diag::ext_octal_literal;
1432+
Diags.Report(TokLoc, DiagId);
1433+
++s;
1434+
DigitsBegin = s;
1435+
SawOctalPrefix = true;
1436+
}
1437+
1438+
auto _ = llvm::make_scope_exit([&] {
1439+
// If we still have an octal value but we did not see an octal prefix,
1440+
// diagnose as being an obsolescent feature starting in C2y.
1441+
if (radix == 8 && LangOpts.C2y && !SawOctalPrefix && !hadError)
1442+
Diags.Report(TokLoc, diag::warn_unprefixed_octal_deprecated);
1443+
});
1444+
14261445
// For now, the radix is set to 8. If we discover that we have a
14271446
// floating point constant, the radix will change to 10. Octal floating
14281447
// point constants are not permitted (only decimal and hexadecimal).

clang/test/C/C2y/n3353.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// RUN: %clang_cc1 -verify=expected,c2y,c -pedantic -std=c2y %s
2+
// RUN: %clang_cc1 -verify=expected,c2y,compat -Wpre-c2y-compat -std=c2y %s
3+
// RUN: %clang_cc1 -verify=expected,ext,c -pedantic -std=c23 %s
4+
// RUN: %clang_cc1 -verify=expected,cpp -pedantic -x c++ -Wno-c11-extensions %s
5+
6+
7+
/* WG14 N3353: Clang 21
8+
* Obsolete implicitly octal literals and add delimited escape sequences
9+
*/
10+
11+
constexpr int i = 0234; // c2y-warning {{octal literals without a '0o' prefix are deprecated}}
12+
constexpr int j = 0o234; /* ext-warning {{octal integer literals are a C2y extension}}
13+
cpp-warning {{octal integer literals are a Clang extension}}
14+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
15+
*/
16+
17+
static_assert(i == 156);
18+
static_assert(j == 156);
19+
20+
// Show that 0O is the same as Oo (tested above)
21+
static_assert(0O1234 == 0o1234); /* ext-warning 2 {{octal integer literals are a C2y extension}}
22+
cpp-warning 2 {{octal integer literals are a Clang extension}}
23+
compat-warning 2 {{octal integer literals are incompatible with standards before C2y}}
24+
*/
25+
26+
// Show that you can use them with the usual integer literal suffixes.
27+
static_assert(0o234ull == 156); /* ext-warning {{octal integer literals are a C2y extension}}
28+
cpp-warning {{octal integer literals are a Clang extension}}
29+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
30+
*/
31+
32+
// And it's still a valid null pointer constant.
33+
static const void *ptr = 0o0; /* ext-warning {{octal integer literals are a C2y extension}}
34+
cpp-warning {{octal integer literals are a Clang extension}}
35+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
36+
*/
37+
38+
// Demonstrate that it works fine in the preprocessor.
39+
#if 0o123 != 0x53 /* ext-warning {{octal integer literals are a C2y extension}}
40+
cpp-warning {{octal integer literals are a Clang extension}}
41+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
42+
*/
43+
#error "oh no, math stopped working!"
44+
#endif
45+
46+
// 0 by itself is not deprecated, of course.
47+
int k = 0;
48+
49+
// Make sure there are no surprises with auto and type deduction. Promotion
50+
// turns this into an 'int', and 'constexpr' implies 'const'.
51+
constexpr auto l = 0o1234567; /* ext-warning {{octal integer literals are a C2y extension}}
52+
cpp-warning {{octal integer literals are a Clang extension}}
53+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
54+
*/
55+
static_assert(l == 0x53977);
56+
static_assert(__extension__ _Generic(typeof(0o1), typeof(01) : 1, default : 0)); /* c2y-warning {{octal literals without a '0o' prefix are deprecated}}
57+
compat-warning {{passing a type argument as the first operand to '_Generic' is incompatible with C standards before C2y}}
58+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
59+
*/
60+
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}}
61+
62+
// Note that 0o by itself is an invalid literal.
63+
int m = 0o; /* expected-error {{invalid suffix 'o' on integer constant}}
64+
c2y-warning {{octal literals without a '0o' prefix are deprecated}}
65+
*/
66+
67+
// Ensure negation works as expected.
68+
static_assert(-0o1234 == -668); /* ext-warning {{octal integer literals are a C2y extension}}
69+
cpp-warning {{octal integer literals are a Clang extension}}
70+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
71+
*/
72+
73+
// FIXME: it would be better to not diagnose the compat and ext warnings when
74+
// the octal literal is invalid.
75+
// We expect diagnostics for non-octal digits.
76+
int n = 0o18; /* expected-error {{invalid digit '8' in octal constant}}
77+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
78+
ext-warning {{octal integer literals are a C2y extension}}
79+
cpp-warning {{octal integer literals are a Clang extension}}
80+
*/
81+
int o1 = 0o8; /* expected-error {{invalid suffix 'o8' on integer constant}}
82+
c2y-warning {{octal literals without a '0o' prefix are deprecated}}
83+
*/
84+
// FIXME: however, it matches the behavior for hex literals in terms of the
85+
// error reported. Unfortunately, we then go on to think 0 is an octal literal
86+
// without a prefix, which is again a bit confusing.
87+
int o2 = 0xG; /* expected-error {{invalid suffix 'xG' on integer constant}}
88+
c2y-warning {{octal literals without a '0o' prefix are deprecated}}
89+
*/
90+
91+
// Ensure digit separators work as expected.
92+
constexpr int p = 0o0'1'2'3'4'5'6'7; /* compat-warning {{octal integer literals are incompatible with standards before C2y}}
93+
ext-warning {{octal integer literals are a C2y extension}}
94+
cpp-warning {{octal integer literals are a Clang extension}}
95+
*/
96+
static_assert(p == 01234567); // c2y-warning {{octal literals without a '0o' prefix are deprecated}}
97+
int q = 0o'0'1; /* expected-error {{invalid suffix 'o'0'1' on integer constant}}
98+
c2y-warning {{octal literals without a '0o' prefix are deprecated}}
99+
*/
100+
101+
#define M 0o123
102+
int r = M; /* compat-warning {{octal integer literals are incompatible with standards before C2y}}
103+
ext-warning {{octal integer literals are a C2y extension}}
104+
cpp-warning {{octal integer literals are a Clang extension}}
105+
*/
106+
107+
// Also, test delimited escape sequences. Note, this paper added a delimited
108+
// escape sequence for octal *and* hex.
109+
auto a = "\x{12}\o{12}\N{SPARKLES}"; /* compat-warning 2 {{delimited escape sequences are incompatible with C standards before C2y}}
110+
ext-warning 2 {{delimited escape sequences are a C2y extension}}
111+
cpp-warning 2 {{delimited escape sequences are a C++23 extension}}
112+
cpp-warning {{named escape sequences are a C++23 extension}}
113+
c-warning {{named escape sequences are a Clang extension}}
114+
*/
115+
116+
#ifdef __cplusplus
117+
template <unsigned N>
118+
struct S {
119+
static_assert(N == 0o567); /* ext-warning {{octal integer literals are a C2y extension}}
120+
cpp-warning {{octal integer literals are a Clang extension}}
121+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
122+
*/
123+
};
124+
125+
void foo() {
126+
S<0o567> s; /* ext-warning {{octal integer literals are a C2y extension}}
127+
cpp-warning {{octal integer literals are a Clang extension}}
128+
compat-warning {{octal integer literals are incompatible with standards before C2y}}
129+
*/
130+
}
131+
#endif
132+
133+
#line 0123 // expected-warning {{#line directive interprets number as decimal, not octal}}
134+
#line 0o123 // expected-error {{#line directive requires a simple digit sequence}}

clang/www/c_status.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ <h2 id="c2y">C2y implementation status</h2>
176176
<tr>
177177
<td>Obsolete implicitly octal literals and add delimited escape sequences</td>
178178
<td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3353.htm">N3353</a></td>
179-
<td class="none" align="center">No</td>
179+
<td class="unreleased" align="center">Clang 21</td>
180180
</tr>
181181
<tr>
182182
<td>'if' declarations, v2</td>

0 commit comments

Comments
 (0)