diff --git a/CHANGES b/CHANGES index a91568b4cb3..577d9cf821e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,17 @@ +Release 3.0.3 (released Apr 26, 2020) +===================================== + +Features added +-------------- + +* C, parse array declarators with static, qualifiers, and VLA specification. + +Bugs fixed +---------- + +* #7516: autodoc: crashes if target object raises an error on accessing + its attributes + Release 3.0.2 (released Apr 19, 2020) ===================================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index cae2025d52d..96b1bd4adec 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -32,8 +32,8 @@ warnings.filterwarnings('ignore', "'U' mode is deprecated", DeprecationWarning, module='docutils.io') -__version__ = '3.0.2' -__released__ = '3.0.2' # used when Sphinx builds its own docs +__version__ = '3.0.3' +__released__ = '3.0.3' # used when Sphinx builds its own docs #: Version info for better programmatic use. #: @@ -43,7 +43,7 @@ #: #: .. versionadded:: 1.2 #: Before version 1.2, check the string ``sphinx.__version__``. -version_info = (3, 0, 2, 'final', 0) +version_info = (3, 0, 3, 'final', 0) package_dir = path.abspath(path.dirname(__file__)) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 53dd3ab2a21..6593b6db38c 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -791,20 +791,60 @@ def _add(modifiers: List[Node], text: str) -> None: ################################################################################ class ASTArray(ASTBase): - def __init__(self, size: ASTExpression): + def __init__(self, static: bool, const: bool, volatile: bool, restrict: bool, + vla: bool, size: ASTExpression): + self.static = static + self.const = const + self.volatile = volatile + self.restrict = restrict + self.vla = vla self.size = size + if vla: + assert size is None + if size is not None: + assert not vla def _stringify(self, transform: StringifyTransform) -> str: - if self.size: - return '[' + transform(self.size) + ']' - else: - return '[]' + el = [] + if self.static: + el.append('static') + if self.restrict: + el.append('restrict') + if self.volatile: + el.append('volatile') + if self.const: + el.append('const') + if self.vla: + return '[' + ' '.join(el) + '*]' + elif self.size: + el.append(transform(self.size)) + return '[' + ' '.join(el) + ']' def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: verify_description_mode(mode) signode.append(nodes.Text("[")) - if self.size: + addSpace = False + + def _add(signode: TextElement, text: str) -> bool: + if addSpace: + signode += nodes.Text(' ') + signode += addnodes.desc_annotation(text, text) + return True + + if self.static: + addSpace = _add(signode, 'static') + if self.restrict: + addSpace = _add(signode, 'restrict') + if self.volatile: + addSpace = _add(signode, 'volatile') + if self.const: + addSpace = _add(signode, 'const') + if self.vla: + signode.append(nodes.Text('*')) + elif self.size: + if addSpace: + signode += nodes.Text(' ') self.size.describe_signature(signode, mode, env, symbol) signode.append(nodes.Text("]")) @@ -2587,18 +2627,45 @@ def _parse_declarator_name_suffix( self.skip_ws() if typed and self.skip_string('['): self.skip_ws() - if self.skip_string(']'): - arrayOps.append(ASTArray(None)) - continue - - def parser(): - return self._parse_expression() + static = False + const = False + volatile = False + restrict = False + while True: + if not static: + if self.skip_word_and_ws('static'): + static = True + continue + if not const: + if self.skip_word_and_ws('const'): + const = True + continue + if not volatile: + if self.skip_word_and_ws('volatile'): + volatile = True + continue + if not restrict: + if self.skip_word_and_ws('restrict'): + restrict = True + continue + break + vla = False if static else self.skip_string_and_ws('*') + if vla: + if not self.skip_string(']'): + self.fail("Expected ']' in end of array operator.") + size = None + else: + if self.skip_string(']'): + size = None + else: - value = self._parse_expression_fallback([']'], parser) - if not self.skip_string(']'): - self.fail("Expected ']' in end of array operator.") - arrayOps.append(ASTArray(value)) - continue + def parser(): + return self._parse_expression() + size = self._parse_expression_fallback([']'], parser) + self.skip_ws() + if not self.skip_string(']'): + self.fail("Expected ']' in end of array operator.") + arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size)) else: break param = self._parse_parameters(paramMode) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 3c1f23bf1f0..855b86d8764 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -578,9 +578,9 @@ def is_filtered_inherited_member(name: str) -> bool: isprivate = membername.startswith('_') keep = False - if getattr(member, '__sphinx_mock__', False): + if safe_getattr(member, '__sphinx_mock__', False): # mocked module or object - keep = False + pass elif want_all and membername.startswith('__') and \ membername.endswith('__') and len(membername) > 4: # special __methods__ diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 009f51644d4..d89e169c358 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -354,6 +354,21 @@ def test_function_definitions(): check('function', 'void f(enum E e)', {1: 'f'}) check('function', 'void f(union E e)', {1: 'f'}) + # array declarators + check('function', 'void f(int arr[])', {1: 'f'}) + check('function', 'void f(int arr[*])', {1: 'f'}) + cvrs = ['', 'const', 'volatile', 'restrict', 'restrict volatile const'] + for cvr in cvrs: + space = ' ' if len(cvr) != 0 else '' + check('function', 'void f(int arr[{}*])'.format(cvr), {1: 'f'}) + check('function', 'void f(int arr[{}])'.format(cvr), {1: 'f'}) + check('function', 'void f(int arr[{}{}42])'.format(cvr, space), {1: 'f'}) + check('function', 'void f(int arr[static{}{} 42])'.format(space, cvr), {1: 'f'}) + check('function', 'void f(int arr[{}{}static 42])'.format(cvr, space), {1: 'f'}, + output='void f(int arr[static{}{} 42])'.format(space, cvr)) + check('function', 'void f(int arr[const static volatile 42])', {1: 'f'}, + output='void f(int arr[static volatile const 42])') + def test_union_definitions(): check('struct', 'A', {1: 'A'})