chiark / gitweb /
documentation/python: make `parse_pybind_type` more permissive.
authorSergei Izmailov <sergei.a.izmailov@gmail.com>
Sat, 16 May 2020 00:42:35 +0000 (03:42 +0300)
committerVladimír Vondruš <mosra@centrum.cz>
Mon, 8 Jun 2020 17:42:29 +0000 (19:42 +0200)
pybind11 has some non-standard typing-like annotations, e.g.:
List[List[float[3]][3]] for std::array<std::array<float,3>,3>

documentation/python.py
documentation/test_python/test_pybind.py

index 8aefd15321b0faede9916e2fdabcbdeb307c54f0..21fa51714b7f39b213cc8e7ff1e2589bb43aa73d 100755 (executable)
@@ -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
index f3af802817ec74b1c3085bc9ef527465f1bd7ab7..9c166da7e7822fe5a4fc90a7827488ae7214098a 100644 (file)
@@ -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<int>'), bad_signature)
+
     def test_crazy_stuff(self):
         self.assertEqual(parse_pybind_signature(self.state, [],
             'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),