Skip to content

Commit

Permalink
documentation/python: support insane pybind 2.6 enum printing.
Browse files Browse the repository at this point in the history
What the hell, why.
  • Loading branch information
mosra committed Jan 3, 2022
1 parent dfde446 commit c6707e1
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
42 changes: 38 additions & 4 deletions documentation/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,19 +785,53 @@ def make_name_link(state: State, referrer_path: List[str], name) -> str:
_pybind_arg_name_rx = re.compile('[*a-zA-Z0-9_]+')
_pybind_type_rx = re.compile('[a-zA-Z0-9_.]+')

def _pybind11_default_argument_length(string):
"""Returns length of balanced []()-expression at begin of input string until `,` or `)`"""
def _pybind11_extract_default_argument(string):
"""Consumes a balanced []()-expression at begin of input string until `,`
or `)`, while also replacing all `<Enum.FOO: -12354>` with just
`Enum.FOO`."""
stack = []
for i, c in enumerate(string):
default = ''
i = 0
while i < len(string):
c = string[i]

# At the end, what follows is the next argument or end of the argument
# list, exit with what we got so far
if len(stack) == 0 and (c == ',' or c == ')'):
return i
return string[i:], default

# Pybind 2.6+ enum in the form of <Enum.FOO_BAR: -2986>, extract
# everything until the colon and discard the rest. It can however be a
# part of a rogue C++ type name, so pick the < only if directly at the
# start or after a space or bracket, and the > only if at the end
# again.
if c == '<' and (i == 0 or string[i - 1] in [' ', '(', '[']):
name_end = string.find(':', i)
if name_end == -1:
raise SyntaxError("Enum expected to have a value: `{}`".format(string))

default += string[i + 1:name_end]
i = string.find('>', name_end + 1) + 1

if i == -1 or (i < len(string) and string[i] not in [',', ')', ']']):
raise SyntaxError("Unexpected content after enum value: `{}`".format(string[i:]))

continue

# Brackets
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))

# If there would be find_first_not_of(), I wouldn't have to iterate
# byte by byte LIKE A CAVEMAN
default += string[i]
i += 1

raise SyntaxError("Unexpected end of `{}`".format(string))

def _pybind_map_name_prefix_or_add_typing_suffix(state: State, input_type: str):
Expand Down
41 changes: 41 additions & 0 deletions documentation/test_python/test_pybind.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,47 @@ def test_default_values_pybind23(self):
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ))'), bad_signature)
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: float = ])'), bad_signature)

# https://github.com/pybind/pybind11/pull/2126, extremely stupid and
# annoying but what can I do. Want to support both this and the original
# behavior in case they revert the insanity again, so test that both
# variants give the same output.
def test_default_values_pybind26(self):
# Before the insane change
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: bar.Enum = Enum.FOO_BAR)'),
('foo', '', [
('a', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
], None, None))

# After the insane change
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: bar.Enum = <Enum.FOO_BAR: -13376>)'),
('foo', '', [
('a', 'bar.Enum', 'bar.Enum', 'Enum.FOO_BAR')
], None, None))

# Nested
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: bar.Enum = (4, [<Enum.FOO_BAR: -13376>], <Enum.FIZZ_PISS: 1>))'),
('foo', '', [
('a', 'bar.Enum', 'bar.Enum', '(4, [Enum.FOO_BAR], Enum.FIZZ_PISS)')
], None, None))

# This isn't really expected to happen but yeah it still treats it as
# an enum
self.assertEqual(parse_pybind_signature(self.state, [],
'foo(a: Enum = <Enum_MISSING_DOT:>)'),
('foo', '', [
('a', 'Enum', 'Enum', 'Enum_MISSING_DOT')
], None, None))

# This will fail
bad_signature = ('foo', '', [('…', None, None, None)], None, None)
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.MISSING_COLON>)'), bad_signature)
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.MISSING_GT)'), bad_signature)
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER>a)'), bad_signature)
self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: Enum = <Enum.CHARACTERS_AFTER><)'), 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)
Expand Down

0 comments on commit c6707e1

Please sign in to comment.