diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index fe52d881d3f..972cbb30593 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, Type, TypeVar, Union, Optional + Any, Callable, Dict, Generator, Iterator, List, Tuple, Type, TypeVar, Union, Optional, + cast ) from docutils import nodes @@ -298,6 +299,8 @@ _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*\] @@ -4662,8 +4665,21 @@ def _parse_literal(self) -> ASTLiteral: integer_literal_re, octal_literal_re]: pos = self.pos if self.match(regex): - while self.current_char in 'uUlLfF': - self.pos += 1 + 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: + 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() @@ -6914,7 +6930,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' 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..7351d6a4f76 --- /dev/null +++ b/tests/roots/test-domain-cpp/udl-negative.rst @@ -0,0 +1,18 @@ +Invalid user-defined literal suffixes +===================================== + +.. cpp:var:: constexpr auto bad_mixed = 1.0f_qs + +.. cpp:var:: constexpr auto bad_order = 1llu_qs + +.. cpp:var:: constexpr auto bad_digit_suffix = 1e-34q1 + +.. cpp:var:: constexpr auto bad_symbol = 8_invalid* + +.. note:: + + 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 new file mode 100644 index 00000000000..99fdfacede1 --- /dev/null +++ b/tests/roots/test-domain-cpp/udl-numeric.rst @@ -0,0 +1,47 @@ +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: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: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 + + +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 + +.. note:: + + Digit separator examples such as ``1'000_km`` remain unsupported by the + test fixtures due to the base literal regular expressions. 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):