Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions sphinx/domains/cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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*\]
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions tests/roots/test-domain-cpp/conf.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
exclude_patterns = ['_build']
cpp_id_attributes = ['inline']
18 changes: 18 additions & 0 deletions tests/roots/test-domain-cpp/udl-negative.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Invalid user-defined literal suffixes
=====================================

.. cpp:var:: constexpr auto bad_mixed = 1.0f_qs

Choose a reason for hiding this comment

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

[major] This case should not be considered invalid. _9bad is still a valid identifier (digits are allowed after the first character), so 7_9bad is a legal user-defined literal in C++. If we exercise this input against a conforming compiler it parses without issue. Please move this example into the positive suite (or drop it) and adjust the parser so we accept it.

.. 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
47 changes: 47 additions & 0 deletions tests/roots/test-domain-cpp/udl-numeric.rst
Original file line number Diff line number Diff line change
@@ -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.
14 changes: 14 additions & 0 deletions tests/test_domain_cpp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Choose a reason for hiding this comment

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

[major] Once _1q is accepted, this suite should only surface the three truly invalid declarations (bad_mixed, bad_order, bad_ws). Please adjust the expected warning count accordingly.


Choose a reason for hiding this comment

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

[major] Once _9bad is treated as a valid literal the failure count should drop. Please update the negative fixture expectations (likely len(ws) == 3).


@pytest.mark.sphinx(testroot='domain-cpp',
confoverrides={'nitpicky': True, 'strip_signature_backslash': True})
def test_build_domain_cpp_backslash_ok(app, status, warning):
Expand Down