diff --git a/CHANGES b/CHANGES index a0f1bab26..432e114a0 100644 --- a/CHANGES +++ b/CHANGES @@ -53,6 +53,18 @@ Features added * #9097: Optimize the paralell build * #9131: Add :confval:`nitpick_ignore_regex` to ignore nitpicky warnings using regular expressions +* C++, add support for + + - ``inline`` variables, + - ``consteval`` functions, + - ``constinit`` variables, + - ``char8_t``, + - ``explicit()`` specifier, + - digit separators in literals, and + - constraints in placeholder type specifiers, aka. adjective syntax + (e.g., ``Sortable auto &v``). + +* C, add support for digit separators in literals. Bugs fixed diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 14d8cde1b..c94f1d06d 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -319,8 +319,9 @@ _fold_operator_re = re.compile(r'''(?x) # see https://en.cppreference.com/w/cpp/keyword _keywords = [ 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor', - 'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class', - 'compl', 'concept', 'const', 'constexpr', 'const_cast', 'continue', + 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t', + 'class', 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit', + 'const_cast', 'continue', 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else', 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend', 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new', @@ -426,6 +427,7 @@ _id_fundamental_v2 = { 'wchar_t': 'w', 'char32_t': 'Di', 'char16_t': 'Ds', + 'char8_t': 'Du', 'short': 's', 'short int': 's', 'signed short': 's', @@ -1880,9 +1882,11 @@ class ASTTrailingTypeSpecDecltype(ASTTrailingTypeSpec): class ASTTrailingTypeSpecName(ASTTrailingTypeSpec): - def __init__(self, prefix: str, nestedName: ASTNestedName) -> None: + def __init__(self, prefix: str, nestedName: ASTNestedName, + placeholderType: Optional[str]) -> None: self.prefix = prefix self.nestedName = nestedName + self.placeholderType = placeholderType @property def name(self) -> ASTNestedName: @@ -1897,6 +1901,9 @@ class ASTTrailingTypeSpecName(ASTTrailingTypeSpec): res.append(self.prefix) res.append(' ') res.append(transform(self.nestedName)) + if self.placeholderType is not None: + res.append(' ') + res.append(self.placeholderType) return ''.join(res) def describe_signature(self, signode: TextElement, mode: str, @@ -1905,6 +1912,17 @@ class ASTTrailingTypeSpecName(ASTTrailingTypeSpec): signode += addnodes.desc_sig_keyword(self.prefix, self.prefix) signode += addnodes.desc_sig_space() self.nestedName.describe_signature(signode, mode, env, symbol=symbol) + if self.placeholderType is not None: + signode += addnodes.desc_sig_space() + if self.placeholderType == 'auto': + signode += addnodes.desc_sig_keyword('auto', 'auto') + elif self.placeholderType == 'decltype(auto)': + signode += addnodes.desc_sig_keyword('decltype', 'decltype') + signode += addnodes.desc_sig_punctuation('(', '(') + signode += addnodes.desc_sig_keyword('auto', 'auto') + signode += addnodes.desc_sig_punctuation(')', ')') + else: + assert False, self.placeholderType class ASTFunctionParameter(ASTBase): @@ -2099,16 +2117,41 @@ class ASTParametersQualifiers(ASTBase): signode += addnodes.desc_sig_keyword(self.initializer, self.initializer) +class ASTExplicitSpec(ASTBase): + def __init__(self, expr: Optional[ASTExpression]) -> None: + self.expr = expr + + def _stringify(self, transform: StringifyTransform) -> str: + res = ['explicit'] + if self.expr is not None: + res.append('(') + res.append(transform(self.expr)) + res.append(')') + return ''.join(res) + + def describe_signature(self, signode: TextElement, + env: "BuildEnvironment", symbol: "Symbol") -> None: + signode += addnodes.desc_sig_keyword('explicit', 'explicit') + if self.expr is not None: + signode += addnodes.desc_sig_punctuation('(', '(') + self.expr.describe_signature(signode, 'markType', env, symbol) + signode += addnodes.desc_sig_punctuation(')', ')') + + class ASTDeclSpecsSimple(ASTBase): def __init__(self, storage: str, threadLocal: bool, inline: bool, virtual: bool, - explicit: bool, constexpr: bool, volatile: bool, const: bool, - friend: bool, attrs: List[ASTAttribute]) -> None: + explicitSpec: Optional[ASTExplicitSpec], + consteval: bool, constexpr: bool, constinit: bool, + volatile: bool, const: bool, friend: bool, + attrs: List[ASTAttribute]) -> None: self.storage = storage self.threadLocal = threadLocal self.inline = inline self.virtual = virtual - self.explicit = explicit + self.explicitSpec = explicitSpec + self.consteval = consteval self.constexpr = constexpr + self.constinit = constinit self.volatile = volatile self.const = const self.friend = friend @@ -2121,8 +2164,10 @@ class ASTDeclSpecsSimple(ASTBase): self.threadLocal or other.threadLocal, self.inline or other.inline, self.virtual or other.virtual, - self.explicit or other.explicit, + self.explicitSpec or other.explicitSpec, + self.consteval or other.consteval, self.constexpr or other.constexpr, + self.constinit or other.constinit, self.volatile or other.volatile, self.const or other.const, self.friend or other.friend, @@ -2141,17 +2186,22 @@ class ASTDeclSpecsSimple(ASTBase): res.append('friend') if self.virtual: res.append('virtual') - if self.explicit: - res.append('explicit') + if self.explicitSpec: + res.append(transform(self.explicitSpec)) + if self.consteval: + res.append('consteval') if self.constexpr: res.append('constexpr') + if self.constinit: + res.append('constinit') if self.volatile: res.append('volatile') if self.const: res.append('const') return ' '.join(res) - def describe_signature(self, signode: TextElement) -> None: + def describe_signature(self, signode: TextElement, + env: "BuildEnvironment", symbol: "Symbol") -> None: addSpace = False for attr in self.attrs: if addSpace: @@ -2175,10 +2225,17 @@ class ASTDeclSpecsSimple(ASTBase): addSpace = _add(signode, 'friend') if self.virtual: addSpace = _add(signode, 'virtual') - if self.explicit: - addSpace = _add(signode, 'explicit') + if self.explicitSpec: + if addSpace: + signode += addnodes.desc_sig_space() + self.explicitSpec.describe_signature(signode, env, symbol) + addSpace = True + if self.consteval: + addSpace = _add(signode, 'consteval') if self.constexpr: addSpace = _add(signode, 'constexpr') + if self.constinit: + addSpace = _add(signode, 'constinit') if self.volatile: addSpace = _add(signode, 'volatile') if self.const: @@ -2235,7 +2292,7 @@ class ASTDeclSpecs(ASTBase): env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) numChildren = len(signode) - self.leftSpecs.describe_signature(signode) + self.leftSpecs.describe_signature(signode, env, symbol) addSpace = len(signode) != numChildren if self.trailingTypeSpec: @@ -2249,7 +2306,7 @@ class ASTDeclSpecs(ASTBase): if len(str(self.rightSpecs)) > 0: if addSpace: signode += addnodes.desc_sig_space() - self.rightSpecs.describe_signature(signode) + self.rightSpecs.describe_signature(signode, env, symbol) # Declarator @@ -4942,8 +4999,8 @@ class DefinitionParser(BaseParser): # those without signedness and size modifiers # see https://en.cppreference.com/w/cpp/language/types _simple_fundemental_types = ( - 'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int', - 'float', 'double', 'auto' + 'void', 'bool', 'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t', + 'int', 'float', 'double', 'auto' ) _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename') @@ -5815,7 +5872,19 @@ class DefinitionParser(BaseParser): prefix = k break nestedName = self._parse_nested_name() - return ASTTrailingTypeSpecName(prefix, nestedName) + self.skip_ws() + placeholderType = None + if self.skip_word('auto'): + placeholderType = 'auto' + elif self.skip_word_and_ws('decltype'): + if not self.skip_string_and_ws('('): + self.fail("Expected '(' after 'decltype' in placeholder type specifier.") + if not self.skip_word_and_ws('auto'): + self.fail("Expected 'auto' after 'decltype(' in placeholder type specifier.") + if not self.skip_string_and_ws(')'): + self.fail("Expected ')' after 'decltype(auto' in placeholder type specifier.") + placeholderType = 'decltype(auto)' + return ASTTrailingTypeSpecName(prefix, nestedName, placeholderType) def _parse_parameters_and_qualifiers(self, paramMode: str) -> ASTParametersQualifiers: if paramMode == 'new': @@ -5929,14 +5998,24 @@ class DefinitionParser(BaseParser): threadLocal = None inline = None virtual = None - explicit = None + explicitSpec = None + consteval = None constexpr = None + constinit = None volatile = None const = None friend = None attrs = [] while 1: # accept any permutation of a subset of some decl-specs self.skip_ws() + if not const and typed: + const = self.skip_word('const') + if const: + continue + if not volatile and typed: + volatile = self.skip_word('volatile') + if volatile: + continue if not storage: if outer in ('member', 'function'): if self.skip_word('static'): @@ -5952,16 +6031,28 @@ class DefinitionParser(BaseParser): if self.skip_word('register'): storage = 'register' continue - if not threadLocal and outer == 'member': - threadLocal = self.skip_word('thread_local') - if threadLocal: + if not inline and outer in ('function', 'member'): + inline = self.skip_word('inline') + if inline: + continue + if not constexpr and outer in ('member', 'function'): + constexpr = self.skip_word("constexpr") + if constexpr: continue + if outer == 'member': + if not constinit: + constinit = self.skip_word('constinit') + if constinit: + continue + if not threadLocal: + threadLocal = self.skip_word('thread_local') + if threadLocal: + continue if outer == 'function': - # function-specifiers - if not inline: - inline = self.skip_word('inline') - if inline: + if not consteval: + consteval = self.skip_word('consteval') + if consteval: continue if not friend: friend = self.skip_word('friend') @@ -5971,31 +6062,28 @@ class DefinitionParser(BaseParser): virtual = self.skip_word('virtual') if virtual: continue - if not explicit: - explicit = self.skip_word('explicit') + if not explicitSpec: + explicit = self.skip_word_and_ws('explicit') if explicit: + expr: ASTExpression = None + if self.skip_string('('): + expr = self._parse_constant_expression(inTemplate=False) + if not expr: + self.fail("Expected constant expression after '('" + + " in explicit specifier.") + self.skip_ws() + if not self.skip_string(')'): + self.fail("Expected ')' to end explicit specifier.") + explicitSpec = ASTExplicitSpec(expr) continue - - if not constexpr and outer in ('member', 'function'): - constexpr = self.skip_word("constexpr") - if constexpr: - continue - if not volatile and typed: - volatile = self.skip_word('volatile') - if volatile: - continue - if not const and typed: - const = self.skip_word('const') - if const: - continue attr = self._parse_attribute() if attr: attrs.append(attr) continue break return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual, - explicit, constexpr, volatile, const, - friend, attrs) + explicitSpec, consteval, constexpr, constinit, + volatile, const, friend, attrs) def _parse_decl_specs(self, outer: str, typed: bool = True) -> ASTDeclSpecs: if outer: diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py index 2be2e390f..8d0896624 100644 --- a/sphinx/util/cfamily.py +++ b/sphinx/util/cfamily.py @@ -33,10 +33,10 @@ identifier_re = re.compile(r'''(?x) ) [a-zA-Z0-9_]*\b ''') -integer_literal_re = re.compile(r'[1-9][0-9]*') -octal_literal_re = re.compile(r'0[0-7]*') -hex_literal_re = re.compile(r'0[xX][0-9a-fA-F][0-9a-fA-F]*') -binary_literal_re = re.compile(r'0[bB][01][01]*') +integer_literal_re = re.compile(r'[1-9][0-9]*(\'[0-9]+)*') +octal_literal_re = re.compile(r'0[0-7]*(\'[0-7]+)*') +hex_literal_re = re.compile(r'0[xX][0-9a-fA-F]+(\'[0-9a-fA-F]+)*') +binary_literal_re = re.compile(r'0[bB][01]+(\'[01]+)*') integers_literal_suffix_re = re.compile(r'''(?x) # unsigned and/or (long) long, in any order, but at least one of them ( @@ -50,13 +50,14 @@ integers_literal_suffix_re = re.compile(r'''(?x) float_literal_re = re.compile(r'''(?x) [+-]?( # decimal - ([0-9]+[eE][+-]?[0-9]+) - | ([0-9]*\.[0-9]+([eE][+-]?[0-9]+)?) - | ([0-9]+\.([eE][+-]?[0-9]+)?) + ([0-9]+(\'[0-9]+)*[eE][+-]?[0-9]+(\'[0-9]+)*) + | (([0-9]+(\'[0-9]+)*)?\.[0-9]+(\'[0-9]+)*([eE][+-]?[0-9]+(\'[0-9]+)*)?) + | ([0-9]+(\'[0-9]+)*\.([eE][+-]?[0-9]+(\'[0-9]+)*)?) # hex - | (0[xX][0-9a-fA-F]+[pP][+-]?[0-9a-fA-F]+) - | (0[xX][0-9a-fA-F]*\.[0-9a-fA-F]+([pP][+-]?[0-9a-fA-F]+)?) - | (0[xX][0-9a-fA-F]+\.([pP][+-]?[0-9a-fA-F]+)?) + | (0[xX][0-9a-fA-F]+(\'[0-9a-fA-F]+)*[pP][+-]?[0-9a-fA-F]+(\'[0-9a-fA-F]+)*) + | (0[xX]([0-9a-fA-F]+(\'[0-9a-fA-F]+)*)?\. + [0-9a-fA-F]+(\'[0-9a-fA-F]+)*([pP][+-]?[0-9a-fA-F]+(\'[0-9a-fA-F]+)*)?) + | (0[xX][0-9a-fA-F]+(\'[0-9a-fA-F]+)*\.([pP][+-]?[0-9a-fA-F]+(\'[0-9a-fA-F]+)*)?) ) ''') float_literal_suffix_re = re.compile(r'[fFlL]\b') diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index d57738ec4..575b65362 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -155,7 +155,8 @@ def test_domain_c_ast_expressions(): # primary exprCheck('true') exprCheck('false') - ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1'] + ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1', + "0b0'1'0", "00'1'2", "0x0'1'2", "1'2'3"] unsignedSuffix = ['', 'u', 'U'] longSuffix = ['', 'l', 'L', 'll', 'LL'] for i in ints: @@ -170,14 +171,18 @@ def test_domain_c_ast_expressions(): '5e42', '5e+42', '5e-42', '5.', '5.e42', '5.e+42', '5.e-42', '.5', '.5e42', '.5e+42', '.5e-42', - '5.0', '5.0e42', '5.0e+42', '5.0e-42']: + '5.0', '5.0e42', '5.0e+42', '5.0e-42', + "1'2'3e7'8'9", "1'2'3.e7'8'9", + ".4'5'6e7'8'9", "1'2'3.4'5'6e7'8'9"]: expr = e + suffix exprCheck(expr) for e in [ 'ApF', 'Ap+F', 'Ap-F', 'A.', 'A.pF', 'A.p+F', 'A.p-F', '.A', '.ApF', '.Ap+F', '.Ap-F', - 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F']: + 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F', + "A'B'Cp1'2'3", "A'B'C.p1'2'3", + ".D'E'Fp1'2'3", "A'B'C.D'E'Fp1'2'3"]: expr = "0x" + e + suffix exprCheck(expr) exprCheck('"abc\\"cba"') # string diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index a34fec172..1ad216e5a 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -126,6 +126,7 @@ def test_domain_cpp_ast_fundamental_types(): id = t.replace(" ", "-").replace("long", "l").replace("int", "i") id = id.replace("bool", "b").replace("char", "c") id = id.replace("wc_t", "wchar_t").replace("c16_t", "char16_t") + id = id.replace("c8_t", "char8_t") id = id.replace("c32_t", "char32_t") return "f__%s" % id @@ -173,7 +174,8 @@ def test_domain_cpp_ast_expressions(): exprCheck('nullptr', 'LDnE') exprCheck('true', 'L1E') exprCheck('false', 'L0E') - ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1'] + ints = ['5', '0', '075', '0x0123456789ABCDEF', '0XF', '0b1', '0B1', + "0b0'1'0", "00'1'2", "0x0'1'2", "1'2'3"] unsignedSuffix = ['', 'u', 'U'] longSuffix = ['', 'l', 'L', 'll', 'LL'] for i in ints: @@ -186,11 +188,15 @@ def test_domain_cpp_ast_expressions(): decimalFloats = ['5e42', '5e+42', '5e-42', '5.', '5.e42', '5.e+42', '5.e-42', '.5', '.5e42', '.5e+42', '.5e-42', - '5.0', '5.0e42', '5.0e+42', '5.0e-42'] + '5.0', '5.0e42', '5.0e+42', '5.0e-42', + "1'2'3e7'8'9", "1'2'3.e7'8'9", + ".4'5'6e7'8'9", "1'2'3.4'5'6e7'8'9"] hexFloats = ['ApF', 'Ap+F', 'Ap-F', 'A.', 'A.pF', 'A.p+F', 'A.p-F', '.A', '.ApF', '.Ap+F', '.Ap-F', - 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F'] + 'A.B', 'A.BpF', 'A.Bp+F', 'A.Bp-F', + "A'B'Cp1'2'3", "A'B'C.p1'2'3", + ".D'E'Fp1'2'3", "A'B'C.D'E'Fp1'2'3"] for suffix in ['', 'f', 'F', 'l', 'L']: for e in decimalFloats: expr = e + suffix @@ -435,6 +441,9 @@ def test_domain_cpp_ast_member_definitions(): # check('member', 'int b : (true ? 8 : a) = 42', {1: 'b__i', 2: '1b'}) check('member', 'int b : 1 || new int{0}', {1: 'b__i', 2: '1b'}) + check('member', 'inline int n', {1: 'n__i', 2: '1n'}) + check('member', 'constinit int n', {1: 'n__i', 2: '1n'}) + def test_domain_cpp_ast_function_definitions(): check('function', 'void f(volatile int)', {1: "f__iV", 2: "1fVi"}) @@ -565,6 +574,9 @@ def test_domain_cpp_ast_function_definitions(): check("function", "void f(int *volatile const p)", {1: "f__iPVC", 2: "1fPVCi"}) check('function', 'extern int f()', {1: 'f', 2: '1fv'}) + check('function', 'consteval int f()', {1: 'f', 2: '1fv'}) + + check('function', 'explicit(true) void f()', {1: 'f', 2: '1fv'}) check('function', 'decltype(auto) f()', {1: 'f', 2: "1fv"}) @@ -854,6 +866,15 @@ def test_domain_cpp_ast_templates(): check('type', 'template {key}A', {2: 'I_1CE1A'}, key='using') +def test_domain_cpp_ast_placeholder_types(): + check('function', 'void f(Sortable auto &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}) + check('function', 'void f(const Sortable auto &v)', {1: 'f__SortableCR', 2: '1fRK8Sortable'}) + check('function', 'void f(Sortable decltype(auto) &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}) + check('function', 'void f(const Sortable decltype(auto) &v)', {1: 'f__SortableCR', 2: '1fRK8Sortable'}) + check('function', 'void f(Sortable decltype ( auto ) &v)', {1: 'f__SortableR', 2: '1fR8Sortable'}, + output='void f(Sortable decltype(auto) &v)') + + def test_domain_cpp_ast_requires_clauses(): check('function', 'template requires A auto f() -> void requires B', {4: 'I0EIQaa1A1BE1fvv'})