From: Sergei Izmailov Date: Sat, 16 May 2020 00:42:35 +0000 (+0300) Subject: documentation/python: make `parse_pybind_type` more permissive. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=47b8192cf30cf0427ad184a0b769307c7037637c;p=blog.git documentation/python: make `parse_pybind_type` more permissive. pybind11 has some non-standard typing-like annotations, e.g.: List[List[float[3]][3]] for std::array,3> --- diff --git a/documentation/python.py b/documentation/python.py index 8aefd153..21fa5171 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -797,49 +797,56 @@ def _pybind11_default_argument_length(string): raise SyntaxError("Unmatched {} at pos {} in `{}`".format(c, i, string)) raise SyntaxError("Unexpected end of `{}`".format(string)) -def parse_pybind_type(state: State, referrer_path: List[str], signature: str) -> str: - # If this doesn't match, it's because we're in Callable[[arg, ...], retval] +def map_name_prefix_or_add_typing_suffix(state: State, input_type: str): + if input_type in ['Callable', 'Dict', 'Iterator', 'List', 'Optional', 'Set', 'Tuple', 'Union']: + return 'typing.' + input_type + else: + return map_name_prefix(state, input_type) + +def parse_pybind_type(state: State, referrer_path: List[str], signature: str): match = _pybind_type_rx.match(signature) if match: input_type = match.group(0) signature = signature[len(input_type):] - # Prefix types with the typing module to be consistent with pure - # Python annotations and allow them to be linked to - if input_type in ['Callable', 'Dict', 'List', 'Optional', 'Set', 'Tuple', 'Union']: - type = 'typing.' + input_type - type_link = make_name_link(state, referrer_path, type) - else: - type = map_name_prefix(state, input_type) - type_link = make_name_link(state, referrer_path, type) + type = map_name_prefix_or_add_typing_suffix(state, input_type) + type_link = make_name_link(state, referrer_path, type) else: - assert signature[0] == '[' - type = '' - type_link = '' - - # This is a generic type (or the list in Callable) - if signature and signature[0] == '[': - type += '[' - type_link += '[' - signature = signature[1:] - while signature[0] != ']': - signature, inner_type, inner_type_link = parse_pybind_type(state, referrer_path, signature) - type += inner_type - type_link += inner_type_link - - if signature[0] == ']': break - - # Expecting the next item now, if not there, we failed - if not signature.startswith(', '): raise SyntaxError() - signature = signature[2:] - - type += ', ' - type_link += ', ' - - assert signature[0] == ']' - signature = signature[1:] - type += ']' - type_link += ']' - + raise SyntaxError() + + lvl = 0 + i = 0 + while i < len(signature): + c = signature[i] + if c == '[': + i += 1 + lvl += 1 + type += c + type_link += c + continue + if lvl == 0: + break + if c == ']': + i += 1 + lvl -= 1 + type += c + type_link += c + continue + if c in ', ': + i += 1 + type += c + type_link += c + continue + match = _pybind_type_rx.match(signature[i:]) + if match is None: + raise SyntaxError("Bad python type name: {} ".format(signature[i:])) + input_type = match.group(0) + i += len(input_type) + input_type = map_name_prefix_or_add_typing_suffix(state, input_type) + type += input_type + type_link += make_name_link(state, referrer_path, input_type) + if lvl != 0: + raise SyntaxError("Unbalanced [] in python type {}".format(signature)) + signature = signature[i:] return signature, type, type_link # Returns function name, summary, list of arguments (name, type, type with HTML diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index f3af8028..9c166da7 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -209,6 +209,12 @@ class Signature(unittest.TestCase): self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ))'), bad_signature) self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ])'), bad_signature) + def test_bad_return_type(self): + bad_signature = ('foo', '', [('…', None, None, None)], None, None) + self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> List[[]'), bad_signature) + self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> List]'), bad_signature) + self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> ::std::vector'), bad_signature) + def test_crazy_stuff(self): self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),