diff --git a/CHANGES b/CHANGES index 7e09fc787..b48ee5e75 100644 --- a/CHANGES +++ b/CHANGES @@ -31,6 +31,7 @@ Bugs fixed * AssertionError is raised when custom ``citation_reference`` node having classes attribute refers missing citation (refs: #6147) * #2155: Support ``code`` directive +* C++, fix parsing of braced initializers. Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index b099f5260..d17e67f82 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1159,15 +1159,12 @@ class ASTNoexceptExpr(ASTBase): class ASTNewExpr(ASTBase): - def __init__(self, rooted, isNewTypeId, typ, initList, initType): - # type: (bool, bool, ASTType, List[Any], str) -> None + def __init__(self, rooted, isNewTypeId, typ, initList): + # type: (bool, bool, ASTType, Any) -> None self.rooted = rooted self.isNewTypeId = isNewTypeId self.typ = typ self.initList = initList - self.initType = initType - if self.initList is not None: - assert self.initType in ')}' def _stringify(self, transform): # type: (Callable[[Any], str]) -> str @@ -1181,15 +1178,7 @@ class ASTNewExpr(ASTBase): else: assert False if self.initList is not None: - if self.initType == ')': - res.append('(') - first = True - for e in self.initList: - if not first: - res.append(', ') - first = False - res.append(transform(e)) - res.append(self.initType) + res.append(transform(self.initList)) return ''.join(res) def get_id(self, version): @@ -1200,13 +1189,7 @@ class ASTNewExpr(ASTBase): res.append('_') res.append(self.typ.get_id(version)) if self.initList is not None: - if self.initType == ')': - res.append('pi') - for e in self.initList: - res.append(e.get_id(version)) - res.append('E') - else: - assert False + res.append(self.initList.get_id(version)) else: res.append('E') return ''.join(res) @@ -1221,17 +1204,7 @@ class ASTNewExpr(ASTBase): else: assert False if self.initList is not None: - if self.initType == ')': - signode.append(nodes.Text('(')) - first = True - for e in self.initList: - if not first: - signode.append(nodes.Text(', ')) - first = False - e.describe_signature(signode, mode, env, symbol) - signode.append(nodes.Text(')')) - else: - assert False + self.initList.describe_signature(signode, mode, env, symbol) class ASTDeleteExpr(ASTBase): @@ -1323,38 +1296,24 @@ class ASTTypeId(ASTBase): class ASTPostfixCallExpr(ASTBase): - def __init__(self, exprs): - self.exprs = exprs + def __init__(self, lst): + # type: (Union[ASTParenExprList, ASTBracedInitList]) -> None + self.lst = lst def _stringify(self, transform): # type: (Callable[[Any], str]) -> str - res = ['('] - first = True - for e in self.exprs: - if not first: - res.append(', ') - first = False - res.append(transform(e)) - res.append(')') - return ''.join(res) + return transform(self.lst) def get_id(self, idPrefix, version): # type: (str, int) -> str res = ['cl', idPrefix] - for e in self.exprs: + for e in self.lst.exprs: res.append(e.get_id(version)) res.append('E') return ''.join(res) def describe_signature(self, signode, mode, env, symbol): - signode.append(nodes.Text('(')) - first = True - for e in self.exprs: - if not first: - signode.append(nodes.Text(', ')) - first = False - e.describe_signature(signode, mode, env, symbol) - signode.append(nodes.Text(')')) + self.lst.describe_signature(signode, mode, env, symbol) class ASTPostfixArray(ASTBase): @@ -3232,18 +3191,85 @@ class ASTDeclaratorNameParamQual(ASTBase): self.paramQual.describe_signature(signode, mode, env, symbol) -class ASTInitializer(ASTBase): - def __init__(self, value): - self.value = value +class ASTParenExprList(ASTBase): + def __init__(self, exprs): + # type: (List[Any]) -> None + self.exprs = exprs + + def get_id(self, version): + # type: (int) -> str + return "pi%sE" % ''.join(e.get_id(version) for e in self.exprs) def _stringify(self, transform): # type: (Callable[[Any], str]) -> str - return ' = ' + transform(self.value) + exprs = [transform(e) for e in self.exprs] + return '(%s)' % ', '.join(exprs) def describe_signature(self, signode, mode, env, symbol): # type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None _verify_description_mode(mode) - signode.append(nodes.Text(' = ')) + signode.append(nodes.Text('(')) + first = True + for e in self.exprs: + if not first: + signode.append(nodes.Text(', ')) + else: + first = False + e.describe_signature(signode, mode, env, symbol) + signode.append(nodes.Text(')')) + + +class ASTBracedInitList(ASTBase): + def __init__(self, exprs, trailingComma): + # type: (List[Any], bool) -> None + self.exprs = exprs + self.trailingComma = trailingComma + + def get_id(self, version): + # type: (int) -> str + return "il%sE" % ''.join(e.get_id(version) for e in self.exprs) + + def _stringify(self, transform): + # type: (Callable[[Any], str]) -> str + exprs = [transform(e) for e in self.exprs] + trailingComma = ',' if self.trailingComma else '' + return '{%s%s}' % (', '.join(exprs), trailingComma) + + def describe_signature(self, signode, mode, env, symbol): + # type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None + _verify_description_mode(mode) + signode.append(nodes.Text('{')) + first = True + for e in self.exprs: + if not first: + signode.append(nodes.Text(', ')) + else: + first = False + e.describe_signature(signode, mode, env, symbol) + if self.trailingComma: + signode.append(nodes.Text(',')) + signode.append(nodes.Text('}')) + + +class ASTInitializer(ASTBase): + def __init__(self, value, hasAssign=True): + # type: (Any, bool) -> None + self.value = value + self.hasAssign = hasAssign + + def _stringify(self, transform): + # type: (Callable[[Any], str]) -> str + val = transform(self.value) + if self.hasAssign: + return ' = ' + val + else: + return val + + def describe_signature(self, signode, mode, env, symbol): + # type: (addnodes.desc_signature, str, BuildEnvironment, Symbol) -> None + _verify_description_mode(mode) + if self.hasAssign: + signode.append(nodes.Text(' = ')) self.value.describe_signature(signode, 'markType', env, symbol) @@ -4844,35 +4870,67 @@ class DefinitionParser: return res return self._parse_nested_name() - def _parse_expression_list_or_braced_init_list(self): - # type: () -> Tuple[List[Any], str] + def _parse_initializer_list(self, name, open, close): + # type: (str, str, str) -> Tuple[List[Any], bool] + # Parse open and close with the actual initializer-list inbetween + # -> initializer-clause '...'[opt] + # | initializer-list ',' initializer-clause '...'[opt] self.skip_ws() - if self.skip_string_and_ws('('): - close = ')' - name = 'parenthesized expression-list' - elif self.skip_string_and_ws('{'): - close = '}' - name = 'braced-init-list' - self.fail('Sorry, braced-init-list not yet supported.') - else: + if not self.skip_string_and_ws(open): return None, None + if self.skip_string(close): + return [], False + exprs = [] - self.skip_ws() - if not self.skip_string(close): - while True: - self.skip_ws() - expr = self._parse_expression(inTemplate=False) - self.skip_ws() - if self.skip_string('...'): - exprs.append(ASTPackExpansionExpr(expr)) - else: - exprs.append(expr) - self.skip_ws() - if self.skip_string(close): - break - if not self.skip_string(','): - self.fail("Error in %s, expected ',' or '%s'." % (name, close)) - return exprs, close + trailingComma = False + while True: + self.skip_ws() + expr = self._parse_expression(inTemplate=False) + self.skip_ws() + if self.skip_string('...'): + exprs.append(ASTPackExpansionExpr(expr)) + else: + exprs.append(expr) + self.skip_ws() + if self.skip_string(close): + break + if not self.skip_string_and_ws(','): + self.fail("Error in %s, expected ',' or '%s'." % (name, close)) + if self.current_char == close and close == '}': + self.pos += 1 + trailingComma = True + break + return exprs, trailingComma + + def _parse_paren_expression_list(self): + # type: () -> ASTParenExprList + # -> '(' expression-list ')' + # though, we relax it to also allow empty parens + # as it's needed in some cases + # + # expression-list + # -> initializer-list + exprs, trailingComma = self._parse_initializer_list("parenthesized expression-list", + '(', ')') + if exprs is None: + return None + return ASTParenExprList(exprs) + + def _parse_braced_init_list(self): + # type: () -> ASTBracedInitList + # -> '{' initializer-list ','[opt] '}' + # | '{' '}' + exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}') + if exprs is None: + return None + return ASTBracedInitList(exprs, trailingComma) + + def _parse_expression_list_or_braced_init_list(self): + # type: () -> Union[ASTParenExprList, ASTBracedInitList] + paren = self._parse_paren_expression_list() + if paren is not None: + return paren + return self._parse_braced_init_list() def _parse_postfix_expression(self): # -> primary @@ -4980,7 +5038,7 @@ class DefinitionParser: raise self._make_multi_error(errors, header) # and now parse postfixes - postFixes = [] + postFixes = [] # type: List[Any] while True: self.skip_ws() if prefixType in ['expr', 'cast', 'typeid']: @@ -5000,7 +5058,7 @@ class DefinitionParser: self.pos -= 3 else: name = self._parse_nested_name() - postFixes.append(ASTPostfixMember(name)) # type: ignore + postFixes.append(ASTPostfixMember(name)) continue if self.skip_string('->'): if self.skip_string('*'): @@ -5008,21 +5066,17 @@ class DefinitionParser: self.pos -= 3 else: name = self._parse_nested_name() - postFixes.append(ASTPostfixMemberOfPointer(name)) # type: ignore + postFixes.append(ASTPostfixMemberOfPointer(name)) continue if self.skip_string('++'): - postFixes.append(ASTPostfixInc()) # type: ignore + postFixes.append(ASTPostfixInc()) continue if self.skip_string('--'): - postFixes.append(ASTPostfixDec()) # type: ignore + postFixes.append(ASTPostfixDec()) continue - lst, typ = self._parse_expression_list_or_braced_init_list() + lst = self._parse_expression_list_or_braced_init_list() if lst is not None: - if typ == ')': - postFixes.append(ASTPostfixCallExpr(lst)) # type: ignore - else: - assert typ == '}' - assert False + postFixes.append(ASTPostfixCallExpr(lst)) continue break if len(postFixes) == 0: @@ -5105,10 +5159,8 @@ class DefinitionParser: decl = self._parse_declarator(named=False, paramMode="new") else: self.fail("Sorry, parenthesised type-id in new expression not yet supported.") - lst, typ = self._parse_expression_list_or_braced_init_list() - if lst: - assert typ in ")}" - return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst, typ) + lst = self._parse_expression_list_or_braced_init_list() + return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst) # delete-expression pos = self.pos rooted = self.skip_string('::') @@ -5267,7 +5319,7 @@ class DefinitionParser: value = self.matched_text else: # TODO: add handling of more bracket-like things, and quote handling - brackets = {'(': ')', '[': ']', '<': '>'} + brackets = {'(': ')', '{': '}', '[': ']', '<': '>'} symbols = [] # type: List[str] while not self.eof: if (len(symbols) == 0 and self.current_char in end): @@ -5825,30 +5877,52 @@ class DefinitionParser: def _parse_initializer(self, outer=None, allowFallback=True): # type: (str, bool) -> ASTInitializer + # initializer # global vars + # -> brace-or-equal-initializer + # | '(' expression-list ')' + # + # brace-or-equal-initializer # member vars + # -> '=' initializer-clause + # | braced-init-list + # + # initializer-clause # function params, non-type template params (with '=' in front) + # -> assignment-expression + # | braced-init-list + # + # we don't distinguish between global and member vars, so disallow paren: + # + # -> braced-init-list # var only + # | '=' assignment-expression + # | '=' braced-init-list self.skip_ws() - # TODO: support paren and brace initialization for memberObject + if outer == 'member': + bracedInit = self._parse_braced_init_list() + if bracedInit is not None: + return ASTInitializer(bracedInit, hasAssign=False) + if not self.skip_string('='): return None + + bracedInit = self._parse_braced_init_list() + if bracedInit is not None: + return ASTInitializer(bracedInit) + + if outer == 'member': + fallbackEnd = [] # type: List[str] + elif outer == 'templateParam': + fallbackEnd = [',', '>'] + elif outer is None: # function parameter + fallbackEnd = [',', ')'] else: - if outer == 'member': - def parser(): - return self._parse_assignment_expression(inTemplate=False) - value = self._parse_expression_fallback([], parser, - allow=allowFallback) - elif outer == 'templateParam': - def parser(): - return self._parse_assignment_expression(inTemplate=True) - value = self._parse_expression_fallback([',', '>'], parser, - allow=allowFallback) - elif outer is None: # function parameter - def parser(): - return self._parse_assignment_expression(inTemplate=False) - value = self._parse_expression_fallback([',', ')'], parser, - allow=allowFallback) - else: - self.fail("Internal error, initializer for outer '%s' not " - "implemented." % outer) - return ASTInitializer(value) + self.fail("Internal error, initializer for outer '%s' not " + "implemented." % outer) + + inTemplate = outer == 'templateParam' + + def parser(): + return self._parse_assignment_expression(inTemplate=inTemplate) + value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback) + return ASTInitializer(value) def _parse_type(self, named, outer=None): # type: (Union[bool, str], str) -> ASTType diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 0cd3445a5..46019b4a9 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -194,6 +194,8 @@ def test_expressions(): exprCheck('new int()', 'nw_ipiE') exprCheck('new int(5, 42)', 'nw_ipiL5EL42EE') exprCheck('::new int', 'nw_iE') + exprCheck('new int{}', 'nw_iilE') + exprCheck('new int{5, 42}', 'nw_iilL5EL42EE') # delete-expression exprCheck('delete p', 'dl1p') exprCheck('delete [] p', 'da1p') @@ -673,6 +675,40 @@ def test_template_args(): {2: "I0E21enable_if_not_array_t"}) +def test_initializers(): + idsMember = {1: 'v__T', 2:'1v'} + idsFunction = {1: 'f__T', 2: '1f1T'} + idsTemplate = {2: 'I_1TE1fv', 4: 'I_1TE1fvv'} + # no init + check('member', 'T v', idsMember) + check('function', 'void f(T v)', idsFunction) + check('function', 'template void f()', idsTemplate) + # with '=', assignment-expression + check('member', 'T v = 42', idsMember) + check('function', 'void f(T v = 42)', idsFunction) + check('function', 'template void f()', idsTemplate) + # with '=', braced-init + check('member', 'T v = {}', idsMember) + check('function', 'void f(T v = {})', idsFunction) + check('function', 'template void f()', idsTemplate) + check('member', 'T v = {42, 42, 42}', idsMember) + check('function', 'void f(T v = {42, 42, 42})', idsFunction) + check('function', 'template void f()', idsTemplate) + check('member', 'T v = {42, 42, 42,}', idsMember) + check('function', 'void f(T v = {42, 42, 42,})', idsFunction) + check('function', 'template void f()', idsTemplate) + check('member', 'T v = {42, 42, args...}', idsMember) + check('function', 'void f(T v = {42, 42, args...})', idsFunction) + check('function', 'template void f()', idsTemplate) + # without '=', braced-init + check('member', 'T v{}', idsMember) + check('member', 'T v{42, 42, 42}', idsMember) + check('member', 'T v{42, 42, 42,}', idsMember) + check('member', 'T v{42, 42, args...}', idsMember) + # other + check('member', 'T v = T{}', idsMember) + + def test_attributes(): # style: C++ check('member', '[[]] int f', {1: 'f__i', 2: '1f'})