_pybind_name_rx = re.compile('[a-zA-Z0-9_]*')
_pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
_pybind_type_rx = re.compile('[a-zA-Z0-9_.]+')
-_pybind_default_value_rx = re.compile('[^,)]+')
+
+def _pybind11_default_argument_length(string):
+ """Returns length of balanced []()-expression at begin of input string until `,` or `)`"""
+ stack = []
+ for i, c in enumerate(string):
+ if len(stack) == 0 and (c == ',' or c == ')'):
+ return i
+ if c == '(':
+ stack.append(')')
+ elif c == '[':
+ stack.append(']')
+ elif c == ')' or c == ']':
+ if len(stack) == 0 or c != stack.pop():
+ 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]
arg_type = None
arg_type_link = None
- # Default (optional) -- for now take everything until the next comma
- # TODO: ugh, do properly
+ # Default (optional)
# The equals has spaces around since 2.3.0, preserve 2.2 compatibility.
# https://github.com/pybind/pybind11/commit/0826b3c10607c8d96e1d89dc819c33af3799a7b8
if signature.startswith(('=', ' = ')):
signature = signature[1 if signature[0] == '=' else 3:]
- default = _pybind_default_value_rx.match(signature).group(0)
- signature = signature[len(default):]
+ default_length = _pybind11_default_argument_length(signature)
+ default = signature[:default_length]
+ signature = signature[default_length:]
else:
default = None
('b', 'str', 'str', '\'hello\''),
], None, None))
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: float=libA.foo(libB.goo(123), libB.bar + 13) + 2, b=3)'),
+ ('foo', '', [
+ ('a', 'float', 'float', 'libA.foo(libB.goo(123), libB.bar + 13) + 2'),
+ ('b', None, None, '3'),
+ ], None, None))
+
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: List=[1, 2, 3], b: Tuple=(1, 2, 3, "str"))'),
+ ('foo', '', [
+ ('a', 'typing.List', 'typing.List', '[1, 2, 3]'),
+ ('b', "typing.Tuple", "typing.Tuple", '(1, 2, 3, "str")'),
+ ], None, None))
+
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: Tuple[int, ...]=(1,("hello", \'world\'),3,4))'),
+ ('foo', '', [
+ ('a', 'typing.Tuple[int, ...]',
+ 'typing.Tuple[int, ...]',
+ '(1,("hello", \'world\'),3,4)')
+ ], None, None))
+
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: str=[dict(key="A", value=\'B\')["key"][0], None][0])'),
+ ('foo', '', [
+ ('a', 'str', 'str', '[dict(key="A", value=\'B\')["key"][0], None][0]')
+ ], None, None))
+
+ bad_signature = ('foo', '', [('…', None, None, None)], None, None)
+
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float=[0][)'), bad_signature)
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float=()'), bad_signature)
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float=(()'), bad_signature)
+ 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_default_values_pybind23(self):
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: float = 1.0, b: str = \'hello\')'),
('b', 'str', 'str', '\'hello\''),
], None, None))
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: float = libA.foo(libB.goo(123), libB.bar + 13) + 2, b=3)'),
+ ('foo', '', [
+ ('a', 'float', 'float', 'libA.foo(libB.goo(123), libB.bar + 13) + 2'),
+ ('b', None, None, '3'),
+ ], None, None))
+
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: Tuple[int, ...] = (1,("hello", \'world\'),3,4))'),
+ ('foo', '', [
+ ('a', 'typing.Tuple[int, ...]',
+ 'typing.Tuple[int, ...]',
+ '(1,("hello", \'world\'),3,4)')
+ ], None, None))
+
+ self.assertEqual(parse_pybind_signature(self.state, [],
+ 'foo(a: str = [dict(key="A", value=\'B\')["key"][0], None][0])'),
+ ('foo', '', [
+ ('a', 'str', 'str', '[dict(key="A", value=\'B\')["key"][0], None][0]')
+ ], None, None))
+
+ bad_signature = ('foo', '', [('…', None, None, None)], None, None)
+
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = [0][)'), bad_signature)
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ()'), bad_signature)
+ self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = (()'), bad_signature)
+ 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_crazy_stuff(self):
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: int, b: Math::Vector<4, UnsignedInt>)'),