From aacde5032d801a03e4250c108ddadb22bd9fe119 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Mon, 23 Mar 2020 01:46:29 +0900
Subject: [PATCH 01/17] Fix #7364: autosummary: crashed when
autosummary_generate is False
---
CHANGES | 2 ++
sphinx/ext/autosummary/__init__.py | 2 ++
2 files changed, 4 insertions(+)
diff --git a/CHANGES b/CHANGES
index 710ca51da..272bd29b1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,8 @@ Features added
Bugs fixed
----------
+* #7364: autosummary: crashed when :confval:`autosummary_generate` is False
+
Testing
--------
diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py
index 9c550c622..ee44e77d3 100644
--- a/sphinx/ext/autosummary/__init__.py
+++ b/sphinx/ext/autosummary/__init__.py
@@ -722,6 +722,8 @@ def process_generate_options(app: Sphinx) -> None:
env = app.builder.env
genfiles = [env.doc2path(x, base=None) for x in env.found_docs
if os.path.isfile(env.doc2path(x))]
+ elif genfiles is False:
+ pass
else:
ext = list(app.config.source_suffix)
genfiles = [genfile + (ext[0] if not genfile.endswith(tuple(ext)) else '')
From 0b19204e93673aca0235a168b77f37805e86284d Mon Sep 17 00:00:00 2001
From: Tetsuo Koyama
Date: Mon, 23 Mar 2020 17:16:00 +0900
Subject: [PATCH 02/17] :new: pyvista in EXAMPLES
---
EXAMPLES | 1 +
1 file changed, 1 insertion(+)
diff --git a/EXAMPLES b/EXAMPLES
index 1f6bc848b..075fac7b3 100644
--- a/EXAMPLES
+++ b/EXAMPLES
@@ -243,6 +243,7 @@ Documentation using sphinx_rtd_theme
* `PyPy `__
* `python-sqlparse `__
* `PyVISA `__
+* `pyvista `__
* `Read The Docs `__
* `Free your information from their silos (French) `__ (customized)
* `Releases Sphinx extension `__
From 263b7a021c2853d0fde9a1a2604bd49d929c0294 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Wed, 25 Mar 2020 00:25:03 +0900
Subject: [PATCH 03/17] Fix #7370: autosummary: raises UnboundLocalError when
unknown module given
---
CHANGES | 2 ++
sphinx/ext/autosummary/generate.py | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGES b/CHANGES
index 710ca51da..4d99e2c59 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,8 @@ Features added
Bugs fixed
----------
+* #7370: autosummary: raises UnboundLocalError when unknown module given
+
Testing
--------
diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py
index 5a87e4abf..932856aa6 100644
--- a/sphinx/ext/autosummary/generate.py
+++ b/sphinx/ext/autosummary/generate.py
@@ -279,7 +279,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None,
try:
name, obj, parent, mod_name = import_by_name(entry.name)
except ImportError as e:
- _warn(__('[autosummary] failed to import %r: %s') % (name, e))
+ _warn(__('[autosummary] failed to import %r: %s') % (entry.name, e))
continue
content = generate_autosummary_content(name, obj, parent, template, entry.template,
From 077df4a0848b6c791c83215af34dddbc13517b34 Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Tue, 24 Mar 2020 17:26:02 +0100
Subject: [PATCH 04/17] C++, support alternate spellings of operators
Fixes sphinx-doc/sphinx#7367
---
CHANGES | 1 +
sphinx/domains/cpp.py | 76 +++++++++++++++++++++-------------
tests/test_domain_cpp.py | 89 +++++++++++++++++++++++++++++-----------
3 files changed, 115 insertions(+), 51 deletions(-)
diff --git a/CHANGES b/CHANGES
index 4d99e2c59..652db65ab 100644
--- a/CHANGES
+++ b/CHANGES
@@ -17,6 +17,7 @@ Bugs fixed
----------
* #7370: autosummary: raises UnboundLocalError when unknown module given
+* #7367: C++, alternate operator spellings are now supported.
Testing
--------
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 7f7558cb3..b5d9d7b76 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -306,6 +306,7 @@ _operator_re = re.compile(r'''(?x)
| ->\*? | \,
| (<<|>>)=? | && | \|\|
| [!<>=/*%+|&^~-]=?
+ | (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b)
''')
_fold_operator_re = re.compile(r'''(?x)
->\* | \.\* | \,
@@ -464,37 +465,37 @@ _id_operator_v2 = {
# '-(unary)' : 'ng',
# '&(unary)' : 'ad',
# '*(unary)' : 'de',
- '~': 'co',
+ '~': 'co', 'compl': 'co',
'+': 'pl',
'-': 'mi',
'*': 'ml',
'/': 'dv',
'%': 'rm',
- '&': 'an',
- '|': 'or',
- '^': 'eo',
+ '&': 'an', 'bitand': 'an',
+ '|': 'or', 'bitor': 'or',
+ '^': 'eo', 'xor': 'eo',
'=': 'aS',
'+=': 'pL',
'-=': 'mI',
'*=': 'mL',
'/=': 'dV',
'%=': 'rM',
- '&=': 'aN',
- '|=': 'oR',
- '^=': 'eO',
+ '&=': 'aN', 'and_eq': 'aN',
+ '|=': 'oR', 'or_eq': 'oR',
+ '^=': 'eO', 'xor_eq': 'eO',
'<<': 'ls',
'>>': 'rs',
'<<=': 'lS',
'>>=': 'rS',
'==': 'eq',
- '!=': 'ne',
+ '!=': 'ne', 'not_eq': 'ne',
'<': 'lt',
'>': 'gt',
'<=': 'le',
'>=': 'ge',
- '!': 'nt',
- '&&': 'aa',
- '||': 'oo',
+ '!': 'nt', 'not': 'nt',
+ '&&': 'aa', 'and': 'aa',
+ '||': 'oo', 'or': 'oo',
'++': 'pp',
'--': 'mm',
',': 'cm',
@@ -511,8 +512,8 @@ _id_operator_unary_v2 = {
'&': 'ad',
'+': 'ps',
'-': 'ng',
- '!': 'nt',
- '~': 'co'
+ '!': 'nt', 'not': 'nt',
+ '~': 'co', 'compl': 'co'
}
_id_char_from_prefix = {
None: 'c', 'u8': 'c',
@@ -520,21 +521,21 @@ _id_char_from_prefix = {
} # type: Dict[Any, str]
# these are ordered by preceedence
_expression_bin_ops = [
- ['||'],
- ['&&'],
- ['|'],
- ['^'],
- ['&'],
- ['==', '!='],
+ ['||', 'or'],
+ ['&&', 'and'],
+ ['|', 'bitor'],
+ ['^', 'xor'],
+ ['&', 'bitand'],
+ ['==', '!=', 'not_eq'],
['<=', '>=', '<', '>'],
['<<', '>>'],
['+', '-'],
['*', '/', '%'],
['.*', '->*']
]
-_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"]
+_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
- ">>=", "<<=", "&=", "^=", "|="]
+ ">>=", "<<=", "&=", "and_eq", "^=", "|=", "xor_eq", "or_eq"]
_id_explicit_cast = {
'dynamic_cast': 'dc',
'static_cast': 'sc',
@@ -1260,7 +1261,10 @@ class ASTUnaryOpExpr(ASTExpression):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
- return transform(self.op) + transform(self.expr)
+ if self.op[0] in 'cn':
+ return transform(self.op) + " " + transform(self.expr)
+ else:
+ return transform(self.op) + transform(self.expr)
def get_id(self, version: int) -> str:
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
@@ -1268,6 +1272,8 @@ class ASTUnaryOpExpr(ASTExpression):
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.op))
+ if self.op[0] in 'cn':
+ signode.append(nodes.Text(' '))
self.expr.describe_signature(signode, mode, env, symbol)
@@ -1584,6 +1590,8 @@ class ASTOperatorBuildIn(ASTOperator):
def get_id(self, version: int) -> str:
if version == 1:
ids = _id_operator_v1
+ if self.op not in ids:
+ raise NoOldIdError()
else:
ids = _id_operator_v2
if self.op not in ids:
@@ -1592,7 +1600,7 @@ class ASTOperatorBuildIn(ASTOperator):
return ids[self.op]
def _stringify(self, transform: StringifyTransform) -> str:
- if self.op in ('new', 'new[]', 'delete', 'delete[]'):
+ if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox":
return 'operator ' + self.op
else:
return 'operator' + self.op
@@ -5016,7 +5024,11 @@ class DefinitionParser(BaseParser):
self.skip_ws()
for op in _expression_unary_ops:
# TODO: hmm, should we be able to backtrack here?
- if self.skip_string(op):
+ if op[0] in 'cn':
+ res = self.skip_word(op)
+ else:
+ res = self.skip_string(op)
+ if res:
expr = self._parse_cast_expression()
return ASTUnaryOpExpr(op, expr)
if self.skip_word_and_ws('sizeof'):
@@ -5144,8 +5156,12 @@ class DefinitionParser(BaseParser):
pos = self.pos
oneMore = False
for op in _expression_bin_ops[opId]:
- if not self.skip_string(op):
- continue
+ if op[0] in 'abcnox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
if op == '&' and self.current_char == '&':
# don't split the && 'token'
self.pos -= 1
@@ -5187,8 +5203,12 @@ class DefinitionParser(BaseParser):
oneMore = False
self.skip_ws()
for op in _expression_assignment_ops:
- if not self.skip_string(op):
- continue
+ if op[0] in 'anox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
expr = self._parse_logical_or_expression(False)
exprs.append(expr)
ops.append(op)
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index aa8bb97b2..c6d0df8aa 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -197,7 +197,9 @@ def test_expressions():
exprCheck('+5', 'psL5E')
exprCheck('-5', 'ngL5E')
exprCheck('!5', 'ntL5E')
+ exprCheck('not 5', 'ntL5E')
exprCheck('~5', 'coL5E')
+ exprCheck('compl 5', 'coL5E')
exprCheck('sizeof...(a)', 'sZ1a')
exprCheck('sizeof(T)', 'st1T')
exprCheck('sizeof -42', 'szngL42E')
@@ -221,13 +223,19 @@ def test_expressions():
exprCheck('(int)2', 'cviL2E')
# binary op
exprCheck('5 || 42', 'ooL5EL42E')
+ exprCheck('5 or 42', 'ooL5EL42E')
exprCheck('5 && 42', 'aaL5EL42E')
+ exprCheck('5 and 42', 'aaL5EL42E')
exprCheck('5 | 42', 'orL5EL42E')
+ exprCheck('5 bitor 42', 'orL5EL42E')
exprCheck('5 ^ 42', 'eoL5EL42E')
+ exprCheck('5 xor 42', 'eoL5EL42E')
exprCheck('5 & 42', 'anL5EL42E')
+ exprCheck('5 bitand 42', 'anL5EL42E')
# ['==', '!=']
exprCheck('5 == 42', 'eqL5EL42E')
exprCheck('5 != 42', 'neL5EL42E')
+ exprCheck('5 not_eq 42', 'neL5EL42E')
# ['<=', '>=', '<', '>']
exprCheck('5 <= 42', 'leL5EL42E')
exprCheck('A <= 42', 'le1AL42E')
@@ -261,8 +269,11 @@ def test_expressions():
exprCheck('a >>= 5', 'rS1aL5E')
exprCheck('a <<= 5', 'lS1aL5E')
exprCheck('a &= 5', 'aN1aL5E')
+ exprCheck('a and_eq 5', 'aN1aL5E')
exprCheck('a ^= 5', 'eO1aL5E')
+ exprCheck('a xor_eq 5', 'eO1aL5E')
exprCheck('a |= 5', 'oR1aL5E')
+ exprCheck('a or_eq 5', 'oR1aL5E')
# Additional tests
# a < expression that starts with something that could be a template
@@ -531,30 +542,62 @@ def test_function_definitions():
def test_operators():
- check('function', 'void operator new [ ] ()',
- {1: "new-array-operator", 2: "nav"}, output='void operator new[]()')
- check('function', 'void operator delete ()',
- {1: "delete-operator", 2: "dlv"}, output='void operator delete()')
- check('function', 'operator bool() const',
- {1: "castto-b-operatorC", 2: "NKcvbEv"}, output='operator bool() const')
+ check('function', 'void operator new()', {1: "new-operator", 2: "nwv"})
+ check('function', 'void operator new[]()', {1: "new-array-operator", 2: "nav"})
+ check('function', 'void operator delete()', {1: "delete-operator", 2: "dlv"})
+ check('function', 'void operator delete[]()', {1: "delete-array-operator", 2: "dav"})
+ check('function', 'operator bool() const', {1: "castto-b-operatorC", 2: "NKcvbEv"})
+ check('function', 'void operator""_udl()', {2: 'li4_udlv'})
- check('function', 'void operator * ()',
- {1: "mul-operator", 2: "mlv"}, output='void operator*()')
- check('function', 'void operator - ()',
- {1: "sub-operator", 2: "miv"}, output='void operator-()')
- check('function', 'void operator + ()',
- {1: "add-operator", 2: "plv"}, output='void operator+()')
- check('function', 'void operator = ()',
- {1: "assign-operator", 2: "aSv"}, output='void operator=()')
- check('function', 'void operator / ()',
- {1: "div-operator", 2: "dvv"}, output='void operator/()')
- check('function', 'void operator % ()',
- {1: "mod-operator", 2: "rmv"}, output='void operator%()')
- check('function', 'void operator ! ()',
- {1: "not-operator", 2: "ntv"}, output='void operator!()')
-
- check('function', 'void operator "" _udl()',
- {2: 'li4_udlv'}, output='void operator""_udl()')
+ check('function', 'void operator~()', {1: "inv-operator", 2: "cov"})
+ check('function', 'void operator compl()', {2: "cov"})
+ check('function', 'void operator+()', {1: "add-operator", 2: "plv"})
+ check('function', 'void operator-()', {1: "sub-operator", 2: "miv"})
+ check('function', 'void operator*()', {1: "mul-operator", 2: "mlv"})
+ check('function', 'void operator/()', {1: "div-operator", 2: "dvv"})
+ check('function', 'void operator%()', {1: "mod-operator", 2: "rmv"})
+ check('function', 'void operator&()', {1: "and-operator", 2: "anv"})
+ check('function', 'void operator bitand()', {2: "anv"})
+ check('function', 'void operator|()', {1: "or-operator", 2: "orv"})
+ check('function', 'void operator bitor()', {2: "orv"})
+ check('function', 'void operator^()', {1: "xor-operator", 2: "eov"})
+ check('function', 'void operator xor()', {2: "eov"})
+ check('function', 'void operator=()', {1: "assign-operator", 2: "aSv"})
+ check('function', 'void operator+=()', {1: "add-assign-operator", 2: "pLv"})
+ check('function', 'void operator-=()', {1: "sub-assign-operator", 2: "mIv"})
+ check('function', 'void operator*=()', {1: "mul-assign-operator", 2: "mLv"})
+ check('function', 'void operator/=()', {1: "div-assign-operator", 2: "dVv"})
+ check('function', 'void operator%=()', {1: "mod-assign-operator", 2: "rMv"})
+ check('function', 'void operator&=()', {1: "and-assign-operator", 2: "aNv"})
+ check('function', 'void operator and_eq()', {2: "aNv"})
+ check('function', 'void operator|=()', {1: "or-assign-operator", 2: "oRv"})
+ check('function', 'void operator or_eq()', {2: "oRv"})
+ check('function', 'void operator^=()', {1: "xor-assign-operator", 2: "eOv"})
+ check('function', 'void operator xor_eq()', {2: "eOv"})
+ check('function', 'void operator<<()', {1: "lshift-operator", 2: "lsv"})
+ check('function', 'void operator>>()', {1: "rshift-operator", 2: "rsv"})
+ check('function', 'void operator<<=()', {1: "lshift-assign-operator", 2: "lSv"})
+ check('function', 'void operator>>=()', {1: "rshift-assign-operator", 2: "rSv"})
+ check('function', 'void operator==()', {1: "eq-operator", 2: "eqv"})
+ check('function', 'void operator!=()', {1: "neq-operator", 2: "nev"})
+ check('function', 'void operator not_eq()', {2: "nev"})
+ check('function', 'void operator<()', {1: "lt-operator", 2: "ltv"})
+ check('function', 'void operator>()', {1: "gt-operator", 2: "gtv"})
+ check('function', 'void operator<=()', {1: "lte-operator", 2: "lev"})
+ check('function', 'void operator>=()', {1: "gte-operator", 2: "gev"})
+ check('function', 'void operator!()', {1: "not-operator", 2: "ntv"})
+ check('function', 'void operator not()', {2: "ntv"})
+ check('function', 'void operator&&()', {1: "sand-operator", 2: "aav"})
+ check('function', 'void operator and()', {2: "aav"})
+ check('function', 'void operator||()', {1: "sor-operator", 2: "oov"})
+ check('function', 'void operator or()', {2: "oov"})
+ check('function', 'void operator++()', {1: "inc-operator", 2: "ppv"})
+ check('function', 'void operator--()', {1: "dec-operator", 2: "mmv"})
+ check('function', 'void operator,()', {1: "comma-operator", 2: "cmv"})
+ check('function', 'void operator->*()', {1: "pointer-by-pointer-operator", 2: "pmv"})
+ check('function', 'void operator->()', {1: "pointer-operator", 2: "ptv"})
+ check('function', 'void operator()()', {1: "call-operator", 2: "clv"})
+ check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"})
def test_class_definitions():
From c6f1bf66e0adbe003a21b7d3fc480fbc184bf204 Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Tue, 24 Mar 2020 18:38:42 +0100
Subject: [PATCH 05/17] C, alternative spellings of operators
---
CHANGES | 1 +
sphinx/domains/c.py | 45 +++++++++++++++++++++++++++++-------------
tests/test_domain_c.py | 11 +++++++++++
3 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/CHANGES b/CHANGES
index 2b6a8f47b..a75d24e2e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -19,6 +19,7 @@ Bugs fixed
* #7364: autosummary: crashed when :confval:`autosummary_generate` is False
* #7370: autosummary: raises UnboundLocalError when unknown module given
* #7367: C++, alternate operator spellings are now supported.
+* C, alternate operator spellings are now supported.
Testing
--------
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 8f071a9aa..3f98b7261 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -53,21 +53,21 @@ _keywords = [
# these are ordered by preceedence
_expression_bin_ops = [
- ['||'],
- ['&&'],
- ['|'],
- ['^'],
- ['&'],
- ['==', '!='],
+ ['||', 'or'],
+ ['&&', 'and'],
+ ['|', 'bitor'],
+ ['^', 'xor'],
+ ['&', 'bitand'],
+ ['==', '!=', 'not_eq'],
['<=', '>=', '<', '>'],
['<<', '>>'],
['+', '-'],
['*', '/', '%'],
['.*', '->*']
]
-_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"]
+_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
- ">>=", "<<=", "&=", "^=", "|="]
+ ">>=", "<<=", "&=", "and_eq", "^=", "xor_eq", "|=", "or_eq"]
_max_id = 1
_id_prefix = [None, 'c.', 'Cv2.']
@@ -423,11 +423,16 @@ class ASTUnaryOpExpr(ASTExpression):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
- return transform(self.op) + transform(self.expr)
+ if self.op[0] in 'cn':
+ return transform(self.op) + " " + transform(self.expr)
+ else:
+ return transform(self.op) + transform(self.expr)
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode.append(nodes.Text(self.op))
+ if self.op[0] in 'cn':
+ signode.append(nodes.Text(" "))
self.expr.describe_signature(signode, mode, env, symbol)
@@ -2241,7 +2246,11 @@ class DefinitionParser(BaseParser):
self.skip_ws()
for op in _expression_unary_ops:
# TODO: hmm, should we be able to backtrack here?
- if self.skip_string(op):
+ if op[0] in 'cn':
+ res = self.skip_word(op)
+ else:
+ res = self.skip_string(op)
+ if res:
expr = self._parse_cast_expression()
return ASTUnaryOpExpr(op, expr)
if self.skip_word_and_ws('sizeof'):
@@ -2313,8 +2322,12 @@ class DefinitionParser(BaseParser):
pos = self.pos
oneMore = False
for op in _expression_bin_ops[opId]:
- if not self.skip_string(op):
- continue
+ if op[0] in 'abcnox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
if op == '&' and self.current_char == '&':
# don't split the && 'token'
self.pos -= 1
@@ -2353,8 +2366,12 @@ class DefinitionParser(BaseParser):
oneMore = False
self.skip_ws()
for op in _expression_assignment_ops:
- if not self.skip_string(op):
- continue
+ if op[0] in 'abcnox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
expr = self._parse_logical_or_expression()
exprs.append(expr)
ops.append(op)
diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py
index 6d4ba9cea..9daed2800 100644
--- a/tests/test_domain_c.py
+++ b/tests/test_domain_c.py
@@ -172,7 +172,9 @@ def test_expressions():
exprCheck('+5')
exprCheck('-5')
exprCheck('!5')
+ exprCheck('not 5')
exprCheck('~5')
+ exprCheck('compl 5')
exprCheck('sizeof(T)')
exprCheck('sizeof -42')
exprCheck('alignof(T)')
@@ -180,13 +182,19 @@ def test_expressions():
exprCheck('(int)2')
# binary op
exprCheck('5 || 42')
+ exprCheck('5 or 42')
exprCheck('5 && 42')
+ exprCheck('5 and 42')
exprCheck('5 | 42')
+ exprCheck('5 bitor 42')
exprCheck('5 ^ 42')
+ exprCheck('5 xor 42')
exprCheck('5 & 42')
+ exprCheck('5 bitand 42')
# ['==', '!=']
exprCheck('5 == 42')
exprCheck('5 != 42')
+ exprCheck('5 not_eq 42')
# ['<=', '>=', '<', '>']
exprCheck('5 <= 42')
exprCheck('5 >= 42')
@@ -215,8 +223,11 @@ def test_expressions():
exprCheck('a >>= 5')
exprCheck('a <<= 5')
exprCheck('a &= 5')
+ exprCheck('a and_eq 5')
exprCheck('a ^= 5')
+ exprCheck('a xor_eq 5')
exprCheck('a |= 5')
+ exprCheck('a or_eq 5')
def test_type_definitions():
From 1795cda56de3d641a050538a7f8e332136f3284b Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Tue, 24 Mar 2020 20:37:24 +0100
Subject: [PATCH 06/17] C, C++, improve error messages.
---
sphinx/domains/c.py | 16 +++++++++++++---
sphinx/domains/cpp.py | 27 ++++++++++++++++-----------
sphinx/util/cfamily.py | 12 ++++++++----
3 files changed, 37 insertions(+), 18 deletions(-)
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 3f98b7261..5360b3a9b 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -1986,6 +1986,10 @@ class DefinitionParser(BaseParser):
_prefix_keys = ('struct', 'enum', 'union')
+ @property
+ def language(self) -> str:
+ return 'C'
+
def _parse_string(self) -> str:
if self.current_char != '"':
return None
@@ -2697,7 +2701,7 @@ class DefinitionParser(BaseParser):
restrict=restrict, volatile=volatile, const=const,
attrs=attrs)
if typed and self.current_char == '(': # note: peeking, not skipping
- # maybe this is the beginning of params,try that first,
+ # maybe this is the beginning of params, try that first,
# otherwise assume it's noptr->declarator > ( ptr-declarator )
pos = self.pos
try:
@@ -2706,7 +2710,10 @@ class DefinitionParser(BaseParser):
typed)
return res
except DefinitionError as exParamQual:
- prevErrors.append((exParamQual, "If declId and parameters"))
+ msg = "If declarator-id with parameters"
+ if paramMode == 'function':
+ msg += " (e.g., 'void f(int arg)')"
+ prevErrors.append((exParamQual, msg))
self.pos = pos
try:
assert self.current_char == '('
@@ -2723,7 +2730,10 @@ class DefinitionParser(BaseParser):
return ASTDeclaratorParen(inner=inner, next=next)
except DefinitionError as exNoPtrParen:
self.pos = pos
- prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
+ msg = "If parenthesis in noptr-declarator"
+ if paramMode == 'function':
+ msg += " (e.g., 'void (*f(int arg))(double)')"
+ prevErrors.append((exNoPtrParen, msg))
header = "Error in declarator"
raise self._make_multi_error(prevErrors, header)
pos = self.pos
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index b5d9d7b76..51bd41754 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -4576,6 +4576,10 @@ class DefinitionParser(BaseParser):
super().__init__(definition, location=location)
self.config = config
+ @property
+ def language(self) -> str:
+ return 'C++'
+
def _parse_string(self) -> str:
if self.current_char != '"':
return None
@@ -5470,7 +5474,7 @@ class DefinitionParser(BaseParser):
self.skip_ws()
if not self.skip_string('('):
if paramMode == 'function':
- self.fail('Expecting "(" in parameters_and_qualifiers.')
+ self.fail('Expecting "(" in parameters-and-qualifiers.')
else:
return None
args = []
@@ -5483,7 +5487,7 @@ class DefinitionParser(BaseParser):
self.skip_ws()
if not self.skip_string(')'):
self.fail('Expected ")" after "..." in '
- 'parameters_and_qualifiers.')
+ 'parameters-and-qualifiers.')
break
# note: it seems that function arguments can always be named,
# even in function pointers and similar.
@@ -5498,7 +5502,7 @@ class DefinitionParser(BaseParser):
break
else:
self.fail(
- 'Expecting "," or ")" in parameters_and_qualifiers, '
+ 'Expecting "," or ")" in parameters-and-qualifiers, '
'got "%s".' % self.current_char)
# TODO: why did we have this bail-out?
@@ -5529,7 +5533,7 @@ class DefinitionParser(BaseParser):
exceptionSpec = 'noexcept'
self.skip_ws()
if self.skip_string('('):
- self.fail('Parameterised "noexcept" not implemented.')
+ self.fail('Parameterised "noexcept" not yet implemented.')
self.skip_ws()
override = self.skip_word_and_ws('override')
@@ -5795,14 +5799,15 @@ class DefinitionParser(BaseParser):
typed)
return res
except DefinitionError as exParamQual:
- prevErrors.append((exParamQual, "If declId, parameters, and qualifiers"))
+ prevErrors.append((exParamQual,
+ "If declarator-id with parameters-and-qualifiers"))
self.pos = pos
try:
assert self.current_char == '('
self.skip_string('(')
# TODO: hmm, if there is a name, it must be in inner, right?
- # TODO: hmm, if there must be parameters, they must b
- # inside, right?
+ # TODO: hmm, if there must be parameters, they must be
+ # inside, right?
inner = self._parse_declarator(named, paramMode, typed)
if not self.skip_string(')'):
self.fail("Expected ')' in \"( ptr-declarator )\"")
@@ -5821,7 +5826,7 @@ class DefinitionParser(BaseParser):
except DefinitionError as e:
self.pos = pos
prevErrors.append((e, "If declarator-id"))
- header = "Error in declarator or parameters and qualifiers"
+ header = "Error in declarator or parameters-and-qualifiers"
raise self._make_multi_error(prevErrors, header)
def _parse_initializer(self, outer: str = None, allowFallback: bool = True
@@ -5994,10 +5999,10 @@ class DefinitionParser(BaseParser):
if eExpr is None:
raise eType
errs = []
- errs.append((eExpr, "If default is an expression"))
- errs.append((eType, "If default is a type"))
+ errs.append((eExpr, "If default template argument is an expression"))
+ errs.append((eType, "If default template argument is a type"))
msg = "Error in non-type template parameter"
- msg += " or constrianted template paramter."
+ msg += " or constrained template parameter."
raise self._make_multi_error(errs, msg)
def _parse_type_using(self) -> ASTTypeUsing:
diff --git a/sphinx/util/cfamily.py b/sphinx/util/cfamily.py
index 516221cf9..73bc62d6f 100644
--- a/sphinx/util/cfamily.py
+++ b/sphinx/util/cfamily.py
@@ -154,19 +154,23 @@ class BaseParser:
result = [header, '\n']
for e in errors:
if len(e[1]) > 0:
- ident = ' '
+ indent = ' '
result.append(e[1])
result.append(':\n')
for line in str(e[0]).split('\n'):
if len(line) == 0:
continue
- result.append(ident)
+ result.append(indent)
result.append(line)
result.append('\n')
else:
result.append(str(e[0]))
return DefinitionError(''.join(result))
+ @property
+ def language(self) -> str:
+ raise NotImplementedError
+
def status(self, msg: str) -> None:
# for debugging
indicator = '-' * self.pos + '^'
@@ -176,8 +180,8 @@ class BaseParser:
errors = []
indicator = '-' * self.pos + '^'
exMain = DefinitionError(
- 'Invalid definition: %s [error at %d]\n %s\n %s' %
- (msg, self.pos, self.definition, indicator))
+ 'Invalid %s declaration: %s [error at %d]\n %s\n %s' %
+ (self.language, msg, self.pos, self.definition, indicator))
errors.append((exMain, "Main error"))
for err in self.otherErrors:
errors.append((err, "Potential other error"))
From 1ab1e1c324fd0abc1aa4db28f62d0a30faf2be3f Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Tue, 24 Mar 2020 23:25:19 +0100
Subject: [PATCH 07/17] C++, improve error messages for template
parameter/argument lists
---
sphinx/domains/cpp.py | 72 +++++++++++++++++++++----------------------
1 file changed, 35 insertions(+), 37 deletions(-)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index b5d9d7b76..cdcb0d3b3 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -5334,13 +5334,7 @@ class DefinitionParser(BaseParser):
prevErrors.append((e, "If type argument"))
self.pos = pos
try:
- # actually here we shouldn't use the fallback parser (hence allow=False),
- # because if actually took the < in an expression, then we _will_ fail,
- # which is handled elsewhere. E.g., :cpp:expr:`A <= 0`.
- def parser():
- return self._parse_constant_expression(inTemplate=True)
- value = self._parse_expression_fallback(
- [',', '>'], parser, allow=False)
+ value = self._parse_constant_expression(inTemplate=True)
self.skip_ws()
if self.skip_string('>'):
parsedEnd = True
@@ -5460,7 +5454,6 @@ class DefinitionParser(BaseParser):
if self.skip_word_and_ws(k):
prefix = k
break
-
nestedName = self._parse_nested_name()
return ASTTrailingTypeSpecName(prefix, nestedName)
@@ -5754,32 +5747,6 @@ class DefinitionParser(BaseParser):
if typed and self.skip_string("..."):
next = self._parse_declarator(named, paramMode, False)
return ASTDeclaratorParamPack(next=next)
- if typed: # pointer to member
- pos = self.pos
- try:
- name = self._parse_nested_name(memberPointer=True)
- self.skip_ws()
- if not self.skip_string('*'):
- self.fail("Expected '*' in pointer to member declarator.")
- self.skip_ws()
- except DefinitionError as e:
- self.pos = pos
- prevErrors.append((e, "If pointer to member declarator"))
- else:
- volatile = False
- const = False
- while 1:
- if not volatile:
- volatile = self.skip_word_and_ws('volatile')
- if volatile:
- continue
- if not const:
- const = self.skip_word_and_ws('const')
- if const:
- continue
- break
- next = self._parse_declarator(named, paramMode, typed)
- return ASTDeclaratorMemPtr(name, const, volatile, next=next)
if typed and self.current_char == '(': # note: peeking, not skipping
if paramMode == "operatorCast":
# TODO: we should be able to parse cast operators which return
@@ -5815,9 +5782,40 @@ class DefinitionParser(BaseParser):
prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
header = "Error in declarator"
raise self._make_multi_error(prevErrors, header)
+ if typed: # pointer to member
+ pos = self.pos
+ try:
+ name = self._parse_nested_name(memberPointer=True)
+ self.skip_ws()
+ if not self.skip_string('*'):
+ self.fail("Expected '*' in pointer to member declarator.")
+ self.skip_ws()
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If pointer to member declarator"))
+ else:
+ volatile = False
+ const = False
+ while 1:
+ if not volatile:
+ volatile = self.skip_word_and_ws('volatile')
+ if volatile:
+ continue
+ if not const:
+ const = self.skip_word_and_ws('const')
+ if const:
+ continue
+ break
+ next = self._parse_declarator(named, paramMode, typed)
+ return ASTDeclaratorMemPtr(name, const, volatile, next=next)
pos = self.pos
try:
- return self._parse_declarator_name_suffix(named, paramMode, typed)
+ res = self._parse_declarator_name_suffix(named, paramMode, typed)
+ # this is a heuristic for error messages, for when there is a < after a
+ # nested name, but it was not a successful template argument list
+ if self.current_char == '<':
+ self.otherErrors.append(self._make_multi_error(prevErrors, ""))
+ return res
except DefinitionError as e:
self.pos = pos
prevErrors.append((e, "If declarator-id"))
@@ -5886,7 +5884,6 @@ class DefinitionParser(BaseParser):
raise Exception('Internal error, unknown outer "%s".' % outer)
if outer != 'operatorCast':
assert named
-
if outer in ('type', 'function'):
# We allow type objects to just be a name.
# Some functions don't have normal return types: constructors,
@@ -6080,8 +6077,8 @@ class DefinitionParser(BaseParser):
self.skip_ws()
if not self.skip_string("<"):
self.fail("Expected '<' after 'template'")
+ prevErrors = []
while 1:
- prevErrors = []
self.skip_ws()
if self.skip_word('template'):
# declare a tenplate template parameter
@@ -6134,6 +6131,7 @@ class DefinitionParser(BaseParser):
if self.skip_string('>'):
return ASTTemplateParams(templateParams)
elif self.skip_string(','):
+ prevErrors = []
continue
else:
header = "Error in template parameter list."
From f9d36008e1d2260422945a612851ae51f2a1b4e8 Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Wed, 25 Mar 2020 11:00:24 +0100
Subject: [PATCH 08/17] C++, support comma operator in expressions
---
sphinx/domains/cpp.py | 138 +++++++++++++++++++++++++--------------
tests/test_domain_cpp.py | 3 +
2 files changed, 93 insertions(+), 48 deletions(-)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index cdcb0d3b3..2b4478820 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -1505,8 +1505,38 @@ class ASTBinOpExpr(ASTExpression):
self.exprs[i].describe_signature(signode, mode, env, symbol)
+class ASTBracedInitList(ASTBase):
+ def __init__(self, exprs: List[Union[ASTExpression, "ASTBracedInitList"]],
+ trailingComma: bool) -> None:
+ self.exprs = exprs
+ self.trailingComma = trailingComma
+
+ def get_id(self, version: int) -> str:
+ return "il%sE" % ''.join(e.get_id(version) for e in self.exprs)
+
+ def _stringify(self, transform: StringifyTransform) -> 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: TextElement, mode: str,
+ env: "BuildEnvironment", symbol: "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 ASTAssignmentExpr(ASTExpression):
- def __init__(self, exprs: List[ASTExpression], ops: List[str]):
+ def __init__(self, exprs: List[Union[ASTExpression, ASTBracedInitList]], ops: List[str]):
assert len(exprs) > 0
assert len(exprs) == len(ops) + 1
self.exprs = exprs
@@ -1540,6 +1570,31 @@ class ASTAssignmentExpr(ASTExpression):
self.exprs[i].describe_signature(signode, mode, env, symbol)
+class ASTCommaExpr(ASTExpression):
+ def __init__(self, exprs: List[ASTExpression]):
+ assert len(exprs) > 0
+ self.exprs = exprs
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ', '.join(transform(e) for e in self.exprs)
+
+ def get_id(self, version: int) -> str:
+ id_ = _id_operator_v2[',']
+ res = []
+ for i in range(len(self.exprs) - 1):
+ res.append(id_)
+ res.append(self.exprs[i].get_id(version))
+ res.append(self.exprs[-1].get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: "BuildEnvironment", symbol: "Symbol") -> None:
+ self.exprs[0].describe_signature(signode, mode, env, symbol)
+ for i in range(1, len(self.exprs)):
+ signode.append(nodes.Text(', '))
+ self.exprs[i].describe_signature(signode, mode, env, symbol)
+
+
class ASTFallbackExpr(ASTExpression):
def __init__(self, expr: str):
self.expr = expr
@@ -2641,7 +2696,7 @@ class ASTDeclaratorParen(ASTDeclarator):
##############################################################################################
class ASTPackExpansionExpr(ASTExpression):
- def __init__(self, expr: ASTExpression):
+ def __init__(self, expr: Union[ASTExpression, ASTBracedInitList]):
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
@@ -2658,7 +2713,7 @@ class ASTPackExpansionExpr(ASTExpression):
class ASTParenExprList(ASTBase):
- def __init__(self, exprs: List[ASTExpression]) -> None:
+ def __init__(self, exprs: List[Union[ASTExpression, ASTBracedInitList]]) -> None:
self.exprs = exprs
def get_id(self, version: int) -> str:
@@ -2682,35 +2737,6 @@ class ASTParenExprList(ASTBase):
signode.append(nodes.Text(')'))
-class ASTBracedInitList(ASTBase):
- def __init__(self, exprs: List[ASTExpression], trailingComma: bool) -> None:
- self.exprs = exprs
- self.trailingComma = trailingComma
-
- def get_id(self, version: int) -> str:
- return "il%sE" % ''.join(e.get_id(version) for e in self.exprs)
-
- def _stringify(self, transform: StringifyTransform) -> 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: TextElement, mode: str,
- env: "BuildEnvironment", symbol: "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: Union[ASTExpression, ASTBracedInitList],
hasAssign: bool = True) -> None:
@@ -4751,7 +4777,7 @@ class DefinitionParser(BaseParser):
self.pos = pos
# fall back to a paren expression
try:
- res = self._parse_expression(inTemplate=False)
+ res = self._parse_expression()
self.skip_ws()
if not self.skip_string(')'):
self.fail("Expected ')' in end of parenthesized expression.")
@@ -4799,7 +4825,9 @@ class DefinitionParser(BaseParser):
return None
def _parse_initializer_list(self, name: str, open: str, close: str
- ) -> Tuple[List[ASTExpression], bool]:
+ ) -> Tuple[List[Union[ASTExpression,
+ ASTBracedInitList]],
+ bool]:
# Parse open and close with the actual initializer-list inbetween
# -> initializer-clause '...'[opt]
# | initializer-list ',' initializer-clause '...'[opt]
@@ -4809,11 +4837,11 @@ class DefinitionParser(BaseParser):
if self.skip_string(close):
return [], False
- exprs = [] # type: List[ASTExpression]
+ exprs = [] # type: List[Union[ASTExpression, ASTBracedInitList]]
trailingComma = False
while True:
self.skip_ws()
- expr = self._parse_expression(inTemplate=False)
+ expr = self._parse_initializer_clause()
self.skip_ws()
if self.skip_string('...'):
exprs.append(ASTPackExpansionExpr(expr))
@@ -4843,6 +4871,12 @@ class DefinitionParser(BaseParser):
return None
return ASTParenExprList(exprs)
+ def _parse_initializer_clause(self) -> Union[ASTExpression, ASTBracedInitList]:
+ bracedInitList = self._parse_braced_init_list()
+ if bracedInitList is not None:
+ return bracedInitList
+ return self._parse_assignment_expression(inTemplate=False)
+
def _parse_braced_init_list(self) -> ASTBracedInitList:
# -> '{' initializer-list ','[opt] '}'
# | '{' '}'
@@ -4902,7 +4936,7 @@ class DefinitionParser(BaseParser):
self.fail("Expected '(' in '%s'." % cast)
def parser():
- return self._parse_expression(inTemplate=False)
+ return self._parse_expression()
expr = self._parse_expression_fallback([')'], parser)
self.skip_ws()
if not self.skip_string(")"):
@@ -4923,7 +4957,7 @@ class DefinitionParser(BaseParser):
try:
def parser():
- return self._parse_expression(inTemplate=False)
+ return self._parse_expression()
expr = self._parse_expression_fallback([')'], parser)
prefix = ASTTypeId(expr, isType=False)
if not self.skip_string(')'):
@@ -4970,7 +5004,7 @@ class DefinitionParser(BaseParser):
self.skip_ws()
if prefixType in ['expr', 'cast', 'typeid']:
if self.skip_string_and_ws('['):
- expr = self._parse_expression(inTemplate=False)
+ expr = self._parse_expression()
self.skip_ws()
if not self.skip_string(']'):
self.fail("Expected ']' in end of postfix expression.")
@@ -5061,7 +5095,7 @@ class DefinitionParser(BaseParser):
if self.skip_word_and_ws('noexcept'):
if not self.skip_string_and_ws('('):
self.fail("Expecting '(' after 'noexcept'.")
- expr = self._parse_expression(inTemplate=False)
+ expr = self._parse_expression()
self.skip_ws()
if not self.skip_string(')'):
self.fail("Expecting ')' to end 'noexcept'.")
@@ -5194,7 +5228,7 @@ class DefinitionParser(BaseParser):
# logical-or-expression
# | logical-or-expression "?" expression ":" assignment-expression
# | logical-or-expression assignment-operator initializer-clause
- exprs = []
+ exprs = [] # type: List[Union[ASTExpression, ASTBracedInitList]]
ops = []
orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
exprs.append(orExpr)
@@ -5209,7 +5243,7 @@ class DefinitionParser(BaseParser):
else:
if not self.skip_string(op):
continue
- expr = self._parse_logical_or_expression(False)
+ expr = self._parse_initializer_clause()
exprs.append(expr)
ops.append(op)
oneMore = True
@@ -5226,11 +5260,19 @@ class DefinitionParser(BaseParser):
# TODO: use _parse_conditional_expression_tail
return orExpr
- def _parse_expression(self, inTemplate: bool) -> ASTExpression:
+ def _parse_expression(self) -> ASTExpression:
# -> assignment-expression
# | expression "," assignment-expresion
- # TODO: actually parse the second production
- return self._parse_assignment_expression(inTemplate=inTemplate)
+ exprs = [self._parse_assignment_expression(inTemplate=False)]
+ while True:
+ self.skip_ws()
+ if not self.skip_string(','):
+ break
+ exprs.append(self._parse_assignment_expression(inTemplate=False))
+ if len(exprs) == 1:
+ return exprs[0]
+ else:
+ return ASTCommaExpr(exprs)
def _parse_expression_fallback(self, end: List[str],
parser: Callable[[], ASTExpression],
@@ -5441,7 +5483,7 @@ class DefinitionParser(BaseParser):
if not self.skip_string(')'):
self.fail("Expected ')' after 'decltype(auto'.")
return ASTTrailingTypeSpecDecltypeAuto()
- expr = self._parse_expression(inTemplate=False)
+ expr = self._parse_expression()
self.skip_ws()
if not self.skip_string(')'):
self.fail("Expected ')' after 'decltype('.")
@@ -5685,7 +5727,7 @@ class DefinitionParser(BaseParser):
continue
def parser():
- return self._parse_expression(inTemplate=False)
+ return self._parse_expression()
value = self._parse_expression_fallback([']'], parser)
if not self.skip_string(']'):
self.fail("Expected ']' in end of array operator.")
@@ -6351,7 +6393,7 @@ class DefinitionParser(BaseParser):
def parse_expression(self) -> Union[ASTExpression, ASTType]:
pos = self.pos
try:
- expr = self._parse_expression(False)
+ expr = self._parse_expression()
self.skip_ws()
self.assert_end()
return expr
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index c6d0df8aa..38b1d141e 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -274,6 +274,9 @@ def test_expressions():
exprCheck('a xor_eq 5', 'eO1aL5E')
exprCheck('a |= 5', 'oR1aL5E')
exprCheck('a or_eq 5', 'oR1aL5E')
+ exprCheck('a = {1, 2, 3}', 'aS1ailL1EL2EL3EE')
+ # comma operator
+ exprCheck('a, 5', 'cm1aL5E')
# Additional tests
# a < expression that starts with something that could be a template
From c9fb5ab9ad6c3512899e279c8162cf76bff85b68 Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Wed, 25 Mar 2020 13:08:09 +0100
Subject: [PATCH 09/17] C++, support pack expansion in template argument lists
---
sphinx/domains/cpp.py | 50 ++++++++++++++++++++++++++++++++--------
tests/test_domain_cpp.py | 6 +++++
2 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 2b4478820..63672985f 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -1713,9 +1713,11 @@ class ASTTemplateArgConstant(ASTBase):
class ASTTemplateArgs(ASTBase):
- def __init__(self, args: List[Union["ASTType", ASTTemplateArgConstant]]) -> None:
+ def __init__(self, args: List[Union["ASTType", ASTTemplateArgConstant]],
+ packExpansion: bool) -> None:
assert args is not None
self.args = args
+ self.packExpansion = packExpansion
def get_id(self, version: int) -> str:
if version == 1:
@@ -1727,13 +1729,21 @@ class ASTTemplateArgs(ASTBase):
res = []
res.append('I')
- for a in self.args:
- res.append(a.get_id(version))
+ if len(self.args) > 0:
+ for a in self.args[:-1]:
+ res.append(a.get_id(version))
+ if self.packExpansion:
+ res.append('J')
+ res.append(self.args[-1].get_id(version))
+ if self.packExpansion:
+ res.append('E')
res.append('E')
return ''.join(res)
def _stringify(self, transform: StringifyTransform) -> str:
res = ', '.join(transform(a) for a in self.args)
+ if self.packExpansion:
+ res += '...'
return '<' + res + '>'
def describe_signature(self, signode: TextElement, mode: str,
@@ -1746,6 +1756,8 @@ class ASTTemplateArgs(ASTBase):
signode += nodes.Text(', ')
first = False
a.describe_signature(signode, 'markType', env, symbol=symbol)
+ if self.packExpansion:
+ signode += nodes.Text('...')
signode += nodes.Text('>')
@@ -5351,13 +5363,21 @@ class DefinitionParser(BaseParser):
return ASTOperatorType(type)
def _parse_template_argument_list(self) -> ASTTemplateArgs:
+ # template-argument-list: (but we include the < and > here
+ # template-argument ...[opt]
+ # template-argument-list, template-argument ...[opt]
+ # template-argument:
+ # constant-expression
+ # type-id
+ # id-expression
self.skip_ws()
if not self.skip_string_and_ws('<'):
return None
if self.skip_string('>'):
- return ASTTemplateArgs([])
+ return ASTTemplateArgs([], False)
prevErrors = []
templateArgs = [] # type: List[Union[ASTType, ASTTemplateArgConstant]]
+ packExpansion = False
while 1:
pos = self.pos
parsedComma = False
@@ -5365,12 +5385,17 @@ class DefinitionParser(BaseParser):
try:
type = self._parse_type(named=False)
self.skip_ws()
- if self.skip_string('>'):
+ if self.skip_string_and_ws('...'):
+ packExpansion = True
+ parsedEnd = True
+ if not self.skip_string('>'):
+ self.fail('Expected ">" after "..." in template argument list.')
+ elif self.skip_string('>'):
parsedEnd = True
elif self.skip_string(','):
parsedComma = True
else:
- self.fail('Expected ">" or "," in template argument list.')
+ self.fail('Expected "...>", ">" or "," in template argument list.')
templateArgs.append(type)
except DefinitionError as e:
prevErrors.append((e, "If type argument"))
@@ -5378,12 +5403,17 @@ class DefinitionParser(BaseParser):
try:
value = self._parse_constant_expression(inTemplate=True)
self.skip_ws()
- if self.skip_string('>'):
+ if self.skip_string_and_ws('...'):
+ packExpansion = True
+ parsedEnd = True
+ if not self.skip_string('>'):
+ self.fail('Expected ">" after "..." in template argument list.')
+ elif self.skip_string('>'):
parsedEnd = True
elif self.skip_string(','):
parsedComma = True
else:
- self.fail('Expected ">" or "," in template argument list.')
+ self.fail('Expected "...>", ">" or "," in template argument list.')
templateArgs.append(ASTTemplateArgConstant(value))
except DefinitionError as e:
self.pos = pos
@@ -5393,7 +5423,9 @@ class DefinitionParser(BaseParser):
if parsedEnd:
assert not parsedComma
break
- return ASTTemplateArgs(templateArgs)
+ else:
+ assert not packExpansion
+ return ASTTemplateArgs(templateArgs, packExpansion)
def _parse_nested_name(self, memberPointer: bool = False) -> ASTNestedName:
names = [] # type: List[ASTNestedNameElement]
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 38b1d141e..2faa3d4ea 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -629,6 +629,12 @@ def test_class_definitions():
{2: 'I0E7has_varI1TNSt6void_tIDTadN1T3varEEEEE'})
+ check('class', 'template T',
+ {2: 'IDpE1TIJPFi2TsEEE'})
+ check('class', 'template T<(Is)...>',
+ {2: 'I_DpiE1TIJX(Is)EEE', 3: 'I_DpiE1TIJX2IsEEE'})
+
+
def test_union_definitions():
check('union', 'A', {2: "1A"})
From 6ea9eb687a782e8f8d363642c398907781bda09c Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Wed, 25 Mar 2020 13:16:00 +0100
Subject: [PATCH 10/17] Update changes
---
CHANGES | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGES b/CHANGES
index a75d24e2e..42bae4b82 100644
--- a/CHANGES
+++ b/CHANGES
@@ -20,6 +20,8 @@ Bugs fixed
* #7370: autosummary: raises UnboundLocalError when unknown module given
* #7367: C++, alternate operator spellings are now supported.
* C, alternate operator spellings are now supported.
+* #7368: C++, comma operator in expressions, pack expansion in template
+ argument lists, and more comprehensive error messages in some cases.
Testing
--------
From 67e5edde6db57d2b9e1fbc1398d7ff775ac398a3 Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Wed, 25 Mar 2020 20:07:16 +0100
Subject: [PATCH 11/17] C, C++, do not recurse in anon symbols on addition
---
sphinx/domains/c.py | 2 +-
sphinx/domains/cpp.py | 2 +-
tests/roots/test-domain-c/anon-dup-decl.rst | 5 +++++
tests/roots/test-domain-cpp/anon-dup-decl.rst | 4 ++++
tests/test_domain_c.py | 9 +++++++++
tests/test_domain_cpp.py | 9 +++++++++
6 files changed, 29 insertions(+), 2 deletions(-)
create mode 100644 tests/roots/test-domain-c/anon-dup-decl.rst
create mode 100644 tests/roots/test-domain-cpp/anon-dup-decl.rst
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
index 5360b3a9b..a7b42cfd3 100644
--- a/sphinx/domains/c.py
+++ b/sphinx/domains/c.py
@@ -1675,7 +1675,7 @@ class Symbol:
onMissingQualifiedSymbol,
ancestorLookupType=None,
matchSelf=False,
- recurseInAnon=True,
+ recurseInAnon=False,
searchInSiblings=False)
assert lookupResult is not None # we create symbols all the way, so that can't happen
symbols = list(lookupResult.symbols)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 68e9f4d2f..dbb6d1a39 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -4159,7 +4159,7 @@ class Symbol:
ancestorLookupType=None,
templateShorthand=False,
matchSelf=False,
- recurseInAnon=True,
+ recurseInAnon=False,
correctPrimaryTemplateArgs=True,
searchInSiblings=False)
assert lookupResult is not None # we create symbols all the way, so that can't happen
diff --git a/tests/roots/test-domain-c/anon-dup-decl.rst b/tests/roots/test-domain-c/anon-dup-decl.rst
new file mode 100644
index 000000000..5f6c3bdfe
--- /dev/null
+++ b/tests/roots/test-domain-c/anon-dup-decl.rst
@@ -0,0 +1,5 @@
+.. c:struct:: anon_dup_decl
+
+ .. c:struct:: @a.A
+ .. c:struct:: @b.A
+ .. c:struct:: A
diff --git a/tests/roots/test-domain-cpp/anon-dup-decl.rst b/tests/roots/test-domain-cpp/anon-dup-decl.rst
new file mode 100644
index 000000000..89a9c9553
--- /dev/null
+++ b/tests/roots/test-domain-cpp/anon-dup-decl.rst
@@ -0,0 +1,4 @@
+.. cpp:namespace:: anon_dup_decl
+.. cpp:class:: @a::A
+.. cpp:class:: @b::A
+.. cpp:class:: A
diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py
index 9daed2800..3255efc55 100644
--- a/tests/test_domain_c.py
+++ b/tests/test_domain_c.py
@@ -474,6 +474,15 @@ def test_build_domain_c(app, status, warning):
assert len(ws) == 0
+@pytest.mark.sphinx(testroot='domain-c', confoverrides={'nitpicky': True})
+def test_build_domain_c_anon_dup_decl(app, status, warning):
+ app.builder.build_all()
+ ws = filter_warnings(warning, "anon-dup-decl")
+ assert len(ws) == 2
+ assert "WARNING: c:identifier reference target not found: @a" in ws[0]
+ assert "WARNING: c:identifier reference target not found: @b" in ws[1]
+
+
def test_cfunction(app):
text = (".. c:function:: PyObject* "
"PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems)")
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 2faa3d4ea..53dd4c5a9 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -913,6 +913,15 @@ def test_build_domain_cpp_backslash_ok(app, status, warning):
assert "WARNING: Parsing of expression failed. Using fallback parser." in ws[0]
+@pytest.mark.sphinx(testroot='domain-cpp', confoverrides={'nitpicky': True})
+def test_build_domain_cpp_anon_dup_decl(app, status, warning):
+ app.builder.build_all()
+ ws = filter_warnings(warning, "anon-dup-decl")
+ assert len(ws) == 2
+ assert "WARNING: cpp:identifier reference target not found: @a" in ws[0]
+ assert "WARNING: cpp:identifier reference target not found: @b" in ws[1]
+
+
@pytest.mark.sphinx(testroot='domain-cpp')
def test_build_domain_cpp_misuse_of_roles(app, status, warning):
app.builder.build_all()
From 5be82125f2d462b2d7895539ee234987e3e7c96f Mon Sep 17 00:00:00 2001
From: Jakob Lykke Andersen
Date: Wed, 25 Mar 2020 20:27:51 +0100
Subject: [PATCH 12/17] Update changes
---
CHANGES | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGES b/CHANGES
index 42bae4b82..f0c19c854 100644
--- a/CHANGES
+++ b/CHANGES
@@ -22,6 +22,7 @@ Bugs fixed
* C, alternate operator spellings are now supported.
* #7368: C++, comma operator in expressions, pack expansion in template
argument lists, and more comprehensive error messages in some cases.
+* C, C++, fix crash and wrong duplicate warnings related to anon symbols.
Testing
--------
From f3aae09231237ffc170b256b164b8c752c04bc51 Mon Sep 17 00:00:00 2001
From: Tetsuo Koyama
Date: Sat, 28 Mar 2020 17:56:35 +0900
Subject: [PATCH 13/17] :gift_heart: Let's say thanks to all translator
---
doc/_templates/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/doc/_templates/index.html b/doc/_templates/index.html
index 1fc4ee51c..4290c9d22 100644
--- a/doc/_templates/index.html
+++ b/doc/_templates/index.html
@@ -92,8 +92,8 @@
create a customized documentation using Sphinx written by the matplotlib
developers.{%endtrans%}
- {%trans%}There is a Japanese translation
- of this documentation, thanks to the Japanese Sphinx user group.{%endtrans%}
+ {%trans%}There is a translation team in Transifex
+ of this documentation, thanks to the Sphinx document translators.{%endtrans%}
{%trans%}A Japanese book about Sphinx has been published by O'Reilly:
Sphinxをはじめよう /
Learning Sphinx.{%endtrans%}
From 5ba69ad83774cbb27a6b0b751aa0cf9fc8912f2f Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Sat, 28 Mar 2020 21:53:46 +0900
Subject: [PATCH 14/17] Fix #7225: release package contains .mypy_cache
directory
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 42fe22dd8..0f3cb9a08 100644
--- a/Makefile
+++ b/Makefile
@@ -50,7 +50,7 @@ clean-buildfiles:
.PHONY: clean-mypyfiles
clean-mypyfiles:
- rm -rf .mypy_cache/
+ rm -rf **/.mypy_cache/
.PHONY: style-check
style-check:
From d09dce974e03c74cc8642d23700767db02343d6a Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Sat, 28 Mar 2020 21:54:26 +0900
Subject: [PATCH 15/17] Fix typo
---
Makefile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 0f3cb9a08..eea632143 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ clean-backupfiles:
clean-generated:
find . -name '.DS_Store' -exec rm -f {} +
rm -rf Sphinx.egg-info/
- rm -rf dists/
+ rm -rf dist/
rm -rf doc/_build/
rm -f sphinx/pycode/*.pickle
rm -f utils/*3.py*
From ba4510d01f7340fabf9ae5485cfc10c87b6746e4 Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Sat, 28 Mar 2020 22:01:32 +0900
Subject: [PATCH 16/17] doc: Hide changelog entries on ToC page
---
doc/changes.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/changes.rst b/doc/changes.rst
index e42636872..ab9795572 100644
--- a/doc/changes.rst
+++ b/doc/changes.rst
@@ -1,4 +1,4 @@
-:tocdepth: 2
+:tocdepth: 1
.. default-role:: any
From bdeea0723bd32c2bfc3883ce7494966a77517c3f Mon Sep 17 00:00:00 2001
From: Takeshi KOMIYA
Date: Sat, 28 Mar 2020 22:37:22 +0900
Subject: [PATCH 17/17] Update CONTRIBUTING
---
CONTRIBUTING.rst | 80 ++++++++++++++++++++++++++++--------------------
1 file changed, 47 insertions(+), 33 deletions(-)
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index af12e4a5b..c47487139 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -204,6 +204,14 @@ These are the basic steps needed to start developing on Sphinx.
#. Wait for a core developer to review your changes.
+Translations
+~~~~~~~~~~~~
+
+The Sphinx core messages and documentations are translated on `Transifex
+`_. Please join `Sphinx project on Transifex
+`_ and translate them.
+
+
Core Developers
~~~~~~~~~~~~~~~
@@ -228,39 +236,6 @@ The following are some general guidelines for core developers:
author in the commit message and any relevant :file:`CHANGES` entry.
-Locale updates
-~~~~~~~~~~~~~~
-
-The parts of messages in Sphinx that go into builds are translated into several
-locales. The translations are kept as gettext ``.po`` files translated from the
-master template ``sphinx/locale/sphinx.pot``.
-
-Sphinx uses `Babel `_ to extract messages
-and maintain the catalog files. It is integrated in ``setup.py``:
-
-* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
-* Use ``python setup.py update_catalog`` to update all existing language
- catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
- template file.
-* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
- ``.mo`` files and ``.js`` files.
-
-When an updated ``.po`` file is submitted, run compile_catalog to commit both
-the source and the compiled catalogs.
-
-When a new locale is submitted, add a new directory with the ISO 639-1 language
-identifier and put ``sphinx.po`` in there. Don't forget to update the possible
-values for :confval:`language` in ``doc/usage/configuration.rst``.
-
-The Sphinx core messages can also be translated on `Transifex
-`_. There exists a client tool named ``tx`` in the
-Python package "transifex_client", which can be used to pull translations in
-``.po`` format from Transifex. To do this, go to ``sphinx/locale`` and then run
-``tx pull -f -l LANG`` where LANG is an existing language identifier. It is
-good practice to run ``python setup.py update_catalog`` afterwards to make sure
-the ``.po`` file has the canonical Babel formatting.
-
-
Coding Guide
------------
@@ -439,3 +414,42 @@ and other ``test_*.py`` files under ``tests`` directory.
.. versionadded:: 1.8
Sphinx also runs JavaScript tests.
+
+
+Release procedures
+------------------
+
+The release procedures are listed on ``utils/release-checklist``.
+
+
+Locale Updates
+~~~~~~~~~~~~~~
+
+The parts of messages in Sphinx that go into builds are translated into several
+locales. The translations are kept as gettext ``.po`` files translated from the
+master template :file:`sphinx/locale/sphinx.pot`.
+
+Sphinx uses `Babel `_ to extract messages
+and maintain the catalog files. It is integrated in ``setup.py``:
+
+* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
+* Use ``python setup.py update_catalog`` to update all existing language
+ catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
+ template file.
+* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
+ ``.mo`` files and ``.js`` files.
+
+When an updated ``.po`` file is submitted, run compile_catalog to commit both
+the source and the compiled catalogs.
+
+When a new locale is submitted, add a new directory with the ISO 639-1 language
+identifier and put ``sphinx.po`` in there. Don't forget to update the possible
+values for :confval:`language` in ``doc/usage/configuration.rst``.
+
+The Sphinx core messages can also be translated on `Transifex
+`_. There exists a client tool named ``tx`` in the
+Python package "transifex_client", which can be used to pull translations in
+``.po`` format from Transifex. To do this, go to ``sphinx/locale`` and then run
+``tx pull -f -l LANG`` where LANG is an existing language identifier. It is
+good practice to run ``python setup.py update_catalog`` afterwards to make sure
+the ``.po`` file has the canonical Babel formatting.