From e525009ee53b9855d293e867782dc7d1632ba67b Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Sat, 27 Dec 2025 20:56:23 +0000 Subject: [PATCH 1/5] feat(cpp): support numeric udls [skip ci] --- sphinx/domains/cpp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index fe52d881d3f..28f8ddb082d 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -298,6 +298,7 @@ _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) +_udl_suffix_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") _visibility_re = re.compile(r'\b(public|private|protected)\b') _operator_re = re.compile(r'''(?x) \[\s*\] @@ -4662,8 +4663,11 @@ def _parse_literal(self) -> ASTLiteral: integer_literal_re, octal_literal_re]: pos = self.pos if self.match(regex): + suffix_start = self.pos while self.current_char in 'uUlLfF': self.pos += 1 + if self.pos == suffix_start: + self.match(_udl_suffix_re) return ASTNumberLiteral(self.definition[pos:self.pos]) string = self._parse_string() From efc87a893f48ba0269be429a1abd92d9a48d585c Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Sat, 27 Dec 2025 21:15:07 +0000 Subject: [PATCH 2/5] fix(cpp): add numeric udl coverage [skip ci] --- sphinx/domains/cpp.py | 9 ++++-- tests/roots/test-domain-cpp/conf.py | 1 + tests/roots/test-domain-cpp/udl-negative.rst | 10 +++++++ tests/roots/test-domain-cpp/udl-numeric.rst | 30 ++++++++++++++++++++ tests/test_domain_cpp.py | 14 +++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 tests/roots/test-domain-cpp/udl-negative.rst create mode 100644 tests/roots/test-domain-cpp/udl-numeric.rst diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 28f8ddb082d..56307dab392 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -10,7 +10,7 @@ import re from typing import ( - Any, Callable, Dict, Generator, Iterator, List, Tuple, Type, TypeVar, Union, Optional + Any, Callable, Dict, Generator, Iterator, List, Tuple, TypeVar, Union, Optional ) from docutils import nodes @@ -4667,7 +4667,12 @@ def _parse_literal(self) -> ASTLiteral: while self.current_char in 'uUlLfF': self.pos += 1 if self.pos == suffix_start: - self.match(_udl_suffix_re) + if self.current_char == '_': + next_char = self.definition[self.pos + 1:self.pos + 2] + if next_char and (next_char.isalpha() or next_char == '_'): + self.match(_udl_suffix_re) + elif self.current_char.isalpha(): + self.match(_udl_suffix_re) return ASTNumberLiteral(self.definition[pos:self.pos]) string = self._parse_string() diff --git a/tests/roots/test-domain-cpp/conf.py b/tests/roots/test-domain-cpp/conf.py index a45d22e2821..59b81816d40 100644 --- a/tests/roots/test-domain-cpp/conf.py +++ b/tests/roots/test-domain-cpp/conf.py @@ -1 +1,2 @@ exclude_patterns = ['_build'] +cpp_id_attributes = ['inline'] diff --git a/tests/roots/test-domain-cpp/udl-negative.rst b/tests/roots/test-domain-cpp/udl-negative.rst new file mode 100644 index 00000000000..392ed6eeaa0 --- /dev/null +++ b/tests/roots/test-domain-cpp/udl-negative.rst @@ -0,0 +1,10 @@ +Invalid user-defined literal suffixes +===================================== + +.. cpp:var:: constexpr auto bad_digit_prefix = 7_9bad + +.. cpp:var:: constexpr auto bad_character = 8_invalid* + +.. cpp:var:: constexpr auto bad_whitespace = 9 _unit + +.. cpp:var:: constexpr auto bad_builtin_combo = 10u_unit diff --git a/tests/roots/test-domain-cpp/udl-numeric.rst b/tests/roots/test-domain-cpp/udl-numeric.rst new file mode 100644 index 00000000000..26920348cf3 --- /dev/null +++ b/tests/roots/test-domain-cpp/udl-numeric.rst @@ -0,0 +1,30 @@ +Numeric user-defined literals +============================== + +.. cpp:var:: constexpr auto udl_decimal = 42_km + +.. cpp:var:: constexpr auto udl_hex = 0x2A_km + +.. cpp:var:: constexpr auto udl_binary = 0b1010_unit + +.. cpp:var:: constexpr auto udl_float = 3.14_q_s + +.. cpp:namespace:: units::si + +.. cpp:var:: inline constexpr auto planck_constant = 6.62607015e-34q_J * 1q_s + + +Builtin literal suffix regression +--------------------------------- + +.. cpp:var:: constexpr auto builtin_u = 1u + +.. cpp:var:: constexpr auto builtin_U = 1U + +.. cpp:var:: constexpr auto builtin_l = 1l + +.. cpp:var:: constexpr auto builtin_L = 1L + +.. cpp:var:: constexpr auto builtin_f = 1.0f + +.. cpp:var:: constexpr auto builtin_F = 1.0F diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 9db741ae5d9..3987f622831 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -921,6 +921,20 @@ def test_build_domain_cpp_semicolon(app, status, warning): assert len(ws) == 0 +@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}, freshenv=True) +def test_build_domain_cpp_udl_numeric(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "udl-numeric") + assert len(ws) == 0 + + +@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True}, freshenv=True) +def test_build_domain_cpp_udl_negative(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "udl-negative") + assert len(ws) == 4 + + @pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True, 'strip_signature_backslash': True}) def test_build_domain_cpp_backslash_ok(app, status, warning): From cfce9e01661c88de59f2a109c0891c01b0fb99ce Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Sat, 27 Dec 2025 21:21:41 +0000 Subject: [PATCH 3/5] fix(cpp): restore type import [skip ci] --- sphinx/domains/cpp.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 56307dab392..dfb822ee4a2 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -10,7 +10,8 @@ import re from typing import ( - Any, Callable, Dict, Generator, Iterator, List, Tuple, TypeVar, Union, Optional + Any, Callable, Dict, Generator, Iterator, List, Tuple, Type, TypeVar, Union, Optional, + cast ) from docutils import nodes @@ -6923,7 +6924,7 @@ def __init__(self, asCode: bool) -> None: if asCode: # render the expression as inline code self.class_type = 'cpp-expr' - self.node_type = nodes.literal # type: Type[TextElement] + self.node_type = cast(Type[TextElement], nodes.literal) else: # render the expression as inline text self.class_type = 'cpp-texpr' From 78455e2f58d642726a07c06c2f49c18564c20ffd Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Sat, 27 Dec 2025 21:30:56 +0000 Subject: [PATCH 4/5] fix(cpp): guard builtin suffixes [skip ci] --- sphinx/domains/cpp.py | 17 +++++++++++++---- tests/roots/test-domain-cpp/udl-negative.rst | 13 +++++++++---- tests/roots/test-domain-cpp/udl-numeric.rst | 13 +++++++++++++ 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dfb822ee4a2..2ea7c4f2875 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -300,6 +300,7 @@ _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) _udl_suffix_re = re.compile(r"[A-Za-z_][A-Za-z0-9_]*") +_int_suffix_re = re.compile(r"(?:[uU](?:[lL]{1,2})?|(?:[lL]{1,2})[uU]?)") _visibility_re = re.compile(r'\b(public|private|protected)\b') _operator_re = re.compile(r'''(?x) \[\s*\] @@ -4664,10 +4665,18 @@ def _parse_literal(self) -> ASTLiteral: integer_literal_re, octal_literal_re]: pos = self.pos if self.match(regex): - suffix_start = self.pos - while self.current_char in 'uUlLfF': - self.pos += 1 - if self.pos == suffix_start: + consumed_builtin = False + if regex is float_literal_re: + if not self.eof and self.current_char in 'fFlL': + consumed_builtin = True + self.pos += 1 + else: + if not self.eof: + builtin_match = _int_suffix_re.match(self.definition, self.pos) + if builtin_match: + self.pos = builtin_match.end() + consumed_builtin = True + if (not consumed_builtin) and (not self.eof): if self.current_char == '_': next_char = self.definition[self.pos + 1:self.pos + 2] if next_char and (next_char.isalpha() or next_char == '_'): diff --git a/tests/roots/test-domain-cpp/udl-negative.rst b/tests/roots/test-domain-cpp/udl-negative.rst index 392ed6eeaa0..f35f976dfd1 100644 --- a/tests/roots/test-domain-cpp/udl-negative.rst +++ b/tests/roots/test-domain-cpp/udl-negative.rst @@ -1,10 +1,15 @@ Invalid user-defined literal suffixes ===================================== -.. cpp:var:: constexpr auto bad_digit_prefix = 7_9bad +.. cpp:var:: constexpr auto bad_mixed = 1.0f_qs -.. cpp:var:: constexpr auto bad_character = 8_invalid* +.. cpp:var:: constexpr auto bad_order = 1llu_qs -.. cpp:var:: constexpr auto bad_whitespace = 9 _unit +.. cpp:var:: constexpr auto bad_digit_start = 1e-34_1q -.. cpp:var:: constexpr auto bad_builtin_combo = 10u_unit +.. note:: + + ``1e-34q1`` is a standards-compliant user-defined literal; the variant with + ``_1`` exercises the digit-start failure captured by the parser guard. + +.. cpp:var:: constexpr auto bad_ws = 1 ud diff --git a/tests/roots/test-domain-cpp/udl-numeric.rst b/tests/roots/test-domain-cpp/udl-numeric.rst index 26920348cf3..82f855a29f4 100644 --- a/tests/roots/test-domain-cpp/udl-numeric.rst +++ b/tests/roots/test-domain-cpp/udl-numeric.rst @@ -9,6 +9,14 @@ Numeric user-defined literals .. cpp:var:: constexpr auto udl_float = 3.14_q_s +.. cpp:var:: constexpr auto hf1 = 0x1.0p+2ud + +.. cpp:var:: constexpr auto hf2 = 0x1p2_ud + +.. cpp:var:: constexpr auto iu1 = 42f_s + +.. cpp:var:: constexpr auto iu2 = 10Funit + .. cpp:namespace:: units::si .. cpp:var:: inline constexpr auto planck_constant = 6.62607015e-34q_J * 1q_s @@ -28,3 +36,8 @@ Builtin literal suffix regression .. cpp:var:: constexpr auto builtin_f = 1.0f .. cpp:var:: constexpr auto builtin_F = 1.0F + +.. note:: + + Digit separator examples such as ``1'000_km`` remain unsupported by the + test fixtures due to the base literal regular expressions. From 4c18ac56126b4f4a375eb6559f7463ef0c8da7fb Mon Sep 17 00:00:00 2001 From: Casey Brooks Date: Sat, 27 Dec 2025 21:36:28 +0000 Subject: [PATCH 5/5] fix(cpp): accept digit udls [skip ci] --- sphinx/domains/cpp.py | 11 ++++------- tests/roots/test-domain-cpp/udl-negative.rst | 9 ++++++--- tests/roots/test-domain-cpp/udl-numeric.rst | 4 ++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 2ea7c4f2875..972cbb30593 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -4676,13 +4676,10 @@ def _parse_literal(self) -> ASTLiteral: if builtin_match: self.pos = builtin_match.end() consumed_builtin = True - if (not consumed_builtin) and (not self.eof): - if self.current_char == '_': - next_char = self.definition[self.pos + 1:self.pos + 2] - if next_char and (next_char.isalpha() or next_char == '_'): - self.match(_udl_suffix_re) - elif self.current_char.isalpha(): - self.match(_udl_suffix_re) + if not consumed_builtin: + udl_match = _udl_suffix_re.match(self.definition, self.pos) + if udl_match: + self.pos = udl_match.end() return ASTNumberLiteral(self.definition[pos:self.pos]) string = self._parse_string() diff --git a/tests/roots/test-domain-cpp/udl-negative.rst b/tests/roots/test-domain-cpp/udl-negative.rst index f35f976dfd1..7351d6a4f76 100644 --- a/tests/roots/test-domain-cpp/udl-negative.rst +++ b/tests/roots/test-domain-cpp/udl-negative.rst @@ -5,11 +5,14 @@ Invalid user-defined literal suffixes .. cpp:var:: constexpr auto bad_order = 1llu_qs -.. cpp:var:: constexpr auto bad_digit_start = 1e-34_1q +.. cpp:var:: constexpr auto bad_digit_suffix = 1e-34q1 + +.. cpp:var:: constexpr auto bad_symbol = 8_invalid* .. note:: - ``1e-34q1`` is a standards-compliant user-defined literal; the variant with - ``_1`` exercises the digit-start failure captured by the parser guard. + Literals with digit-prefixed suffixes such as ``1e-34_1q`` are valid and + covered by the positive tests; omitting the underscore as in ``1e-34q1`` is + invalid. .. cpp:var:: constexpr auto bad_ws = 1 ud diff --git a/tests/roots/test-domain-cpp/udl-numeric.rst b/tests/roots/test-domain-cpp/udl-numeric.rst index 82f855a29f4..99fdfacede1 100644 --- a/tests/roots/test-domain-cpp/udl-numeric.rst +++ b/tests/roots/test-domain-cpp/udl-numeric.rst @@ -17,6 +17,10 @@ Numeric user-defined literals .. cpp:var:: constexpr auto iu2 = 10Funit +.. cpp:var:: constexpr auto udl_digit_float = 1e-34_1q + +.. cpp:var:: constexpr auto udl_digit_int = 42_1q + .. cpp:namespace:: units::si .. cpp:var:: inline constexpr auto planck_constant = 6.62607015e-34q_J * 1q_s