Skip to content

Commit

Permalink
Merge branch 'function_fixes' of https://github.com/jakobandersen/bre…
Browse files Browse the repository at this point in the history
  • Loading branch information
vermeeren committed Jan 21, 2021
2 parents b1c8c2c + 765ef83 commit 52a00aa
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 64 deletions.
105 changes: 53 additions & 52 deletions breathe/directives.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import re
import subprocess

from typing import Any, List, Type # noqa
from typing import Any, List, Optional, Type # noqa


class NoMatchingFunctionError(BreatheError):
Expand Down Expand Up @@ -76,13 +76,22 @@ def run(self) -> List[Node]:
return warning.warn('doxygenfunction: %s' % e)

# Extract arguments from the function name.
args = self.parse_args(args)
args = self._parse_args(args)

finder_filter = self.filter_factory.create_function_finder_filter(namespace, function_name)
finder_filter = self.filter_factory.create_function_and_all_friend_finder_filter(
namespace, function_name)

# TODO: find a more specific type for the Doxygen nodes
matches = [] # type: List[Any]
finder.filter_(finder_filter, matches)
matchesAll = [] # type: List[Any]
finder.filter_(finder_filter, matchesAll)
matches = []
for m in matchesAll:
# only take functions and friend functions
# ignore friend classes
node = m[0]
if node.kind == 'friend' and not node.argsstring:
continue
matches.append(m)

# Create it ahead of time as it is cheap and it is ugly to declare it for both exception
# clauses below
Expand All @@ -96,39 +105,26 @@ def run(self) -> List[Node]:
)

try:
node_stack = self.resolve_function(matches, args, project_info)
node_stack = self._resolve_function(matches, args, project_info)
except NoMatchingFunctionError:
return warning.warn('doxygenfunction: Cannot find function "{namespace}{function}" '
'{tail}')
except UnableToResolveFunctionError as error:
message = 'doxygenfunction: Unable to resolve multiple matches for function ' \
'"{namespace}{function}" with arguments ({args}) {tail}.\n' \
message = 'doxygenfunction: Unable to resolve function ' \
'"{namespace}{function}" with arguments {args} {tail}.\n' \
'Potential matches:\n'

# We want to create a string for the console output and a set of docutils nodes
# for rendering into the final output. We handle the final output as a literal string
# with a txt based list of the options.
literal_text = ''

# TODO: We're cheating here with the set() as signatures has repeating entries for some
# reason (failures in the matcher_stack code) so we consolidate them by shoving them in
# a set to remove duplicates. Should be fixed!
signatures = ''
for i, entry in enumerate(sorted(set(error.signatures))):
if i:
literal_text += '\n'
# Replace new lines with a new line & enough spacing to reach the appropriate
# alignment for our simple plain text list
literal_text += '- %s' % entry.replace('\n', '\n ')
signatures += ' - %s\n' % entry.replace('\n', '\n ')
block = nodes.literal_block('', '', nodes.Text(literal_text))
text = ''
for i, entry in enumerate(sorted(error.signatures)):
text += '- %s\n' % entry
block = nodes.literal_block('', '', nodes.Text(text))
formatted_message = warning.format(message)
warning_nodes = [
nodes.paragraph("", "", nodes.Text(formatted_message)),
block
]
result = warning.warn(message, rendered_nodes=warning_nodes,
unformatted_suffix=signatures)
unformatted_suffix=text)
return result

target_handler = create_target_handler(self.options, project_info, self.state.document)
Expand All @@ -137,9 +133,9 @@ def run(self) -> List[Node]:
return self.render(node_stack, project_info, filter_, target_handler, NullMaskFactory(),
self.directive_args)

def parse_args(self, function_description):
def _parse_args(self, function_description: str) -> Optional[cpp.ASTParametersQualifiers]:
if function_description == '':
function_description = '()'
return None

parser = cpp.DefinitionParser(function_description,
location=self.get_source_info(),
Expand All @@ -152,14 +148,14 @@ def parse_args(self, function_description):
continue
declarator = p.arg.type.decl
while hasattr(declarator, 'next'):
declarator = declarator.next
declarator = declarator.next # type: ignore
assert hasattr(declarator, 'declId')
declarator.declId = None
p.arg.init = None
declarator.declId = None # type: ignore
p.arg.init = None # type: ignore
return paramQual

def create_function_signature(self, node_stack, project_info, filter_, target_handler,
mask_factory, directive_args):
def _create_function_signature(self, node_stack, project_info, filter_, target_handler,
mask_factory, directive_args) -> str:
"Standard render process used by subclasses"

try:
Expand All @@ -177,7 +173,8 @@ def create_function_signature(self, node_stack, project_info, filter_, target_ha
return format_parser_error("doxygenclass", e.error, e.filename, self.state,
self.lineno, True)
except FileIOError as e:
return format_parser_error("doxygenclass", e.error, e.filename, self.state, self.lineno)
return format_parser_error("doxygenclass", e.error, e.filename, self.state,
self.lineno, False)

context = RenderContext(node_stack, mask_factory, directive_args)
node = node_stack[0]
Expand All @@ -197,16 +194,12 @@ def create_function_signature(self, node_stack, project_info, filter_, target_ha
ast = parser.parse_declaration('function', 'function')
return str(ast)

def resolve_function(self, matches, args, project_info):
def _resolve_function(self, matches, args: Optional[cpp.ASTParametersQualifiers], project_info):
if not matches:
raise NoMatchingFunctionError()

if len(matches) == 1:
return matches[0]

signatures = []

# Iterate over the potential matches
res = []
candSignatures = []
for entry in matches:
text_options = {'no-link': u'', 'outline': u''}

Expand All @@ -220,21 +213,29 @@ def resolve_function(self, matches, args, project_info):
directive_args = self.directive_args[:]
directive_args[2] = text_options

signature = self.create_function_signature(entry, project_info, filter_, target_handler,
mask_factory, directive_args)
signatures.append(signature)
signature = self._create_function_signature(entry, project_info, filter_,
target_handler,
mask_factory, directive_args)
candSignatures.append(signature)

if args is not None:
match = re.match(r"([^(]*)(.*)", signature)
assert match
_match_args = match.group(2)

match = re.match(r"([^(]*)(.*)", signature)
_match_args = match.group(2)
# Parse the text to find the arguments
match_args = self._parse_args(_match_args)

# Parse the text to find the arguments
match_args = self.parse_args(_match_args)
# Match them against the arg spec
if args != match_args:
continue

# Match them against the arg spec
if args == match_args:
return entry
res.append((entry, signature))

raise UnableToResolveFunctionError(signatures)
if len(res) == 1:
return res[0][0]
else:
raise UnableToResolveFunctionError(candSignatures)


class _DoxygenClassLikeDirective(BaseDirective):
Expand Down
5 changes: 3 additions & 2 deletions breathe/renderer/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1026,16 +1026,17 @@ def create_member_finder_filter(self, namespace: str, name: str, kind: str) -> F
return (parent_is_compound & parent_is_file & node_matches) \
| (parent_is_compound & parent_is_not_file & node_matches)

def create_function_finder_filter(self, namespace: str, name: str) -> Filter:
def create_function_and_all_friend_finder_filter(self, namespace: str, name: str) -> Filter:
parent = Parent()
parent_is_compound = parent.node_type == 'compound'
parent_is_group = parent.kind == 'group'

function_filter = self.create_member_finder_filter(namespace, name, 'function')
friend_filter = self.create_member_finder_filter(namespace, name, 'friend')
# Get matching functions but only ones where the parent is not a group. We want to skip
# function entries in groups as we'll find the same functions in a file's xml output
# elsewhere and having more than one match is confusing for our logic later on.
return function_filter & ~(parent_is_compound & parent_is_group)
return (function_filter | friend_filter) & ~(parent_is_compound & parent_is_group)

def create_enumvalue_finder_filter(self, name: str) -> Filter:
"""Returns a filter which looks for an enumvalue with the specified name."""
Expand Down
14 changes: 8 additions & 6 deletions breathe/renderer/sphinxrenderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,12 +1606,14 @@ def visit_function(self, node) -> List[Node]:
if dom == 'py':
declaration = name + node.get_argsstring()
else:
declaration = ' '.join([
self.create_template_prefix(node),
''.join(n.astext() for n in self.render(node.get_type())), # type: ignore
name,
node.get_argsstring()
])
elements = [self.create_template_prefix(node)]
if node.kind == 'friend':
elements.append('friend')
elements.append(''.join(n.astext()
for n in self.render(node.get_type()))) # type: ignore
elements.append(name)
elements.append(node.get_argsstring())
declaration = ' '.join(elements)
nodes = self.handle_declaration(node, declaration)
return nodes
else:
Expand Down
8 changes: 4 additions & 4 deletions tests/test_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,14 +457,14 @@ def test_resolve_overrides(app):

# Verify that the exact arguments returns one override
for args in argsstrings:
ast_param = cls.parse_args(args)
ret = cls.resolve_function(matches, ast_param, None)
ast_param = cls._parse_args(args)
ret = cls._resolve_function(matches, ast_param, None)

def test_ellipsis(app):
argsstrings, matches = get_matches('ellipsis.xml')
cls = get_directive(app)

# Verify that parsing an ellipsis works
ast_param = cls.parse_args(argsstrings[0])
ret = cls.resolve_function(matches, ast_param, None)
ast_param = cls._parse_args(argsstrings[0])
ret = cls._resolve_function(matches, ast_param, None)

0 comments on commit 52a00aa

Please sign in to comment.