C++, cross-reference update

(see also sphinx-doc/sphinx#2057)
- Elaborate the documentation in regard to templates.
- Add shorthand for template declarations.
- Add shorthand for (full) template specialisations.
- Add better error messages for unparseable references.
This commit is contained in:
Jakob Lykke Andersen
2015-10-14 09:41:52 +09:00
parent 346df54358
commit 5f09ecd4cf
2 changed files with 131 additions and 41 deletions

View File

@@ -764,7 +764,7 @@ The C++ directives support the following info fields (see also :ref:`info-field-
Cross-referencing Cross-referencing
~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~
These roles link to the given object types: These roles link to the given declaration types:
.. rst:role:: cpp:any .. rst:role:: cpp:any
cpp:class cpp:class
@@ -775,19 +775,19 @@ These roles link to the given object types:
cpp:enum cpp:enum
cpp:enumerator cpp:enumerator
Reference a C++ object by name. The name must be properly qualified relative to the Reference a C++ declaration by name (see below for details).
position of the link. The name must be properly qualified relative to the position of the link.
.. note:: .. admonition:: Note on References with Templates Parameters/Arguments
Sphinx's syntax to give references a custom title can interfere with Sphinx's syntax to give references a custom title can interfere with
linking to template classes, if nothing follows the closing angle linking to template classes, if nothing follows the closing angle
bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<T>```. bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<int>```.
This is interpreted as a link to ``T`` with a title of ``MyClass``. This is interpreted as a link to ``int`` with a title of ``MyClass``.
In this case, please escape the opening angle bracket with a backslash, In this case, please escape the opening angle bracket with a backslash,
like this: ``:cpp:class:`MyClass\<T>```. like this: ``:cpp:class:`MyClass\<int>```.
.. admonition:: Note on References .. admonition:: Note on References to Overloaded Functions
It is currently impossible to link to a specific version of an It is currently impossible to link to a specific version of an
overloaded method. Currently the C++ domain is the first domain overloaded method. Currently the C++ domain is the first domain
@@ -796,6 +796,80 @@ These roles link to the given object types:
specific overload. Currently Sphinx will link to the first overloaded specific overload. Currently Sphinx will link to the first overloaded
version of the method / function. version of the method / function.
Declarations without template parameters and template arguments
.................................................................
For linking to non-templated declarations the name must be a nested name,
e.g., ``f`` or ``MyClass::f``.
Templated declarations
......................
Assume the following declarations.
.. cpp:class:: Wrapper
.. cpp:class:: template<typename TOuter> \
Outer
.. cpp:class:: template<typename TInner> \
Inner
In general the reference must include the template paraemter declarations, e.g.,
``template\<typename TOuter> Wrapper::Outer`` (:cpp:class:`template\<typename TOuter> Wrapper::Outer`).
Currently the lookup only succeed if the template parameter identifiers are equal strings. That is,
``template\<typename UOuter> Wrapper::Outer`` will not work.
The inner template class can not be directly referenced, unless the current namespace
is changed or the following shorthand is used.
If a template parameter list is omitted, then the lookup will assume either a template or a non-template,
but not a partial template specialisation.
This means the following references work.
- ``Wrapper::Outer`` (:cpp:class:`Wrapper::Outer`)
- ``Wrapper::Outer::Inner`` (:cpp:class:`Wrapper::Outer::Inner`)
- ``template\<typename TInner> Wrapper::Outer::Inner`` (:cpp:class:`template\<typename TInner> Wrapper::Outer::Inner`)
(Full) Template Specialisations
................................
Assume the following declarations.
.. cpp:class:: template<typename TOuter> \
Outer
.. cpp:class:: template<typename TInner> \
Inner
.. cpp:class:: template<> \
Outer<int>
.. cpp:class:: template<typename TInner> \
Inner
.. cpp:class:: template<> \
Inner<bool>
In general the reference must include a template parameter list for each template argument list.
The full specialisation above can therefore be referenced with ``template\<> Outer\<int>`` (:cpp:class:`template\<> Outer\<int>`)
and ``template\<> template\<> Outer\<int>::Inner\<bool>`` (:cpp:class:`template\<> template\<> Outer\<int>::Inner\<bool>`).
As a shorthand the empty template parameter list can be omitted, e.g., ``Outer\<int>`` (:cpp:class:`Outer\<int>`)
and ``Outer\<int>::Inner\<bool>`` (:cpp:class:`Outer\<int>::Inner\<bool>`).
Partial Template Specialisations
.................................
Assume the following declaration.
.. cpp:class:: template<typename T> \
Outer<T*>
References to partial specialisations must always include the template parameter lists, e.g.,
``template\<typename T> Outer\<T*>`` (:cpp:class:`template\<typename T> Outer\<T*>`).
Currently the lookup only succeed if the template parameter identifiers are equal strings.
The Standard Domain The Standard Domain
------------------- -------------------

View File

@@ -2169,7 +2169,8 @@ class Symbol(object):
return ASTNestedName(names, rooted=False) return ASTNestedName(names, rooted=False)
def _find_named_symbol(self, identifier, templateParams, def _find_named_symbol(self, identifier, templateParams,
templateArgs, operator): templateArgs, operator,
templateShorthand):
assert (identifier is None) != (operator is None) assert (identifier is None) != (operator is None)
for s in self.children: for s in self.children:
if s.identifier != identifier: if s.identifier != identifier:
@@ -2184,8 +2185,13 @@ class Symbol(object):
if text_type(name) != text_type(operator): if text_type(name) != text_type(operator):
continue continue
if (s.templateParams is None) != (templateParams is None): if (s.templateParams is None) != (templateParams is None):
continue if templateParams is not None:
if s.templateParams: # we query with params, they must match params
continue
if not templateShorthand:
# we don't query with params, and we do care about them
continue
if templateParams:
# TODO: do better comparison # TODO: do better comparison
if text_type(s.templateParams) != text_type(templateParams): if text_type(s.templateParams) != text_type(templateParams):
continue continue
@@ -2227,7 +2233,8 @@ class Symbol(object):
symbol = parentSymbol._find_named_symbol(identifier, symbol = parentSymbol._find_named_symbol(identifier,
templateParams, templateParams,
templateArgs, templateArgs,
operator=None) operator=None,
templateShorthand=False)
if symbol is None: if symbol is None:
symbol = Symbol(parent=parentSymbol, identifier=identifier, symbol = Symbol(parent=parentSymbol, identifier=identifier,
templateParams=templateParams, templateParams=templateParams,
@@ -2255,7 +2262,8 @@ class Symbol(object):
symbol = parentSymbol._find_named_symbol(identifier, symbol = parentSymbol._find_named_symbol(identifier,
templateParams, templateParams,
templateArgs, templateArgs,
operator) operator,
templateShorthand=False)
if symbol: if symbol:
if not declaration: if not declaration:
# good, just a scope creation # good, just a scope creation
@@ -2325,12 +2333,15 @@ class Symbol(object):
templateArgs = name.templateArgs templateArgs = name.templateArgs
operator = None operator = None
s = s._find_named_symbol(identifier, templateParams, s = s._find_named_symbol(identifier, templateParams,
templateArgs, operator) templateArgs, operator,
templateShorthand=False)
if not s: if not s:
return None return None
return s return s
def find_name(self, nestedName, templateDecls, specific_specialisation): def find_name(self, nestedName, templateDecls, templateShorthand):
# templateShorthand: missing template parameter lists for templates is ok
# TODO: unify this with the _add_symbols # TODO: unify this with the _add_symbols
# This condition should be checked at the parser level. # This condition should be checked at the parser level.
assert len(templateDecls) <= nestedName.num_templates() + 1 assert len(templateDecls) <= nestedName.num_templates() + 1
@@ -2369,12 +2380,11 @@ class Symbol(object):
symbol = parentSymbol._find_named_symbol(identifier, symbol = parentSymbol._find_named_symbol(identifier,
templateParams, templateParams,
templateArgs, templateArgs,
operator) operator,
templateShorthand=templateShorthand)
if symbol: if symbol:
return symbol return symbol
else: else:
# TODO: search for version without template args,
# if not specific_specialisation
return None return None
else: else:
# there shouldn't be anything inside an operator # there shouldn't be anything inside an operator
@@ -2389,7 +2399,8 @@ class Symbol(object):
symbol = parentSymbol._find_named_symbol(identifier, symbol = parentSymbol._find_named_symbol(identifier,
templateParams, templateParams,
templateArgs, templateArgs,
operator=None) operator=None,
templateShorthand=templateShorthand)
if symbol is None: if symbol is None:
# TODO: maybe search without template args # TODO: maybe search without template args
return None return None
@@ -2503,8 +2514,7 @@ class DefinitionParser(object):
def assert_end(self): def assert_end(self):
self.skip_ws() self.skip_ws()
if not self.eof: if not self.eof:
self.fail('expected end of definition, got %r' % self.fail('Expected end of definition.')
self.definition[self.pos:])
def _parse_expression(self, end): def _parse_expression(self, end):
# Stupidly "parse" an expression. # Stupidly "parse" an expression.
@@ -3195,7 +3205,8 @@ class DefinitionParser(object):
else: else:
return ASTTemplateDeclarationPrefix(templates) return ASTTemplateDeclarationPrefix(templates)
def _check_template_consistency(self, nestedName, templatePrefix): def _check_template_consistency(self, nestedName, templatePrefix,
fullSpecShorthand):
numArgs = nestedName.num_templates() numArgs = nestedName.num_templates()
if not templatePrefix: if not templatePrefix:
numParams = 0 numParams = 0
@@ -3207,15 +3218,16 @@ class DefinitionParser(object):
% (numArgs, numParams)) % (numArgs, numParams))
if numArgs > numParams: if numArgs > numParams:
numExtra = numArgs - numParams numExtra = numArgs - numParams
msg = "Too many template argument lists compared to parameter" \ if not fullSpecShorthand:
" lists. Argument lists: %d, Parameter lists: %d," \ msg = "Too many template argument lists compared to parameter" \
" Extra empty parameters lists prepended: %d." \ " lists. Argument lists: %d, Parameter lists: %d," \
% (numArgs, numParams, numExtra) " Extra empty parameters lists prepended: %d." \
msg += " Declaration:\n\t" % (numArgs, numParams, numExtra)
if templatePrefix: msg += " Declaration:\n\t"
msg += "%s\n\t" % text_type(templatePrefix) if templatePrefix:
msg += text_type(nestedName) msg += "%s\n\t" % text_type(templatePrefix)
self.warn(msg) msg += text_type(nestedName)
self.warn(msg)
newTemplates = [] newTemplates = []
for i in range(numExtra): for i in range(numExtra):
@@ -3273,14 +3285,16 @@ class DefinitionParser(object):
else: else:
assert False assert False
templatePrefix = self._check_template_consistency(declaration.name, templatePrefix = self._check_template_consistency(declaration.name,
templatePrefix) templatePrefix,
fullSpecShorthand=False)
return ASTDeclaration(objectType, visibility, return ASTDeclaration(objectType, visibility,
templatePrefix, declaration) templatePrefix, declaration)
def parse_namespace_object(self): def parse_namespace_object(self):
templatePrefix = self._parse_template_declaration_prefix() templatePrefix = self._parse_template_declaration_prefix()
name = self._parse_nested_name() name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix) templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=False)
res = ASTNamespace(name, templatePrefix) res = ASTNamespace(name, templatePrefix)
res.objectType = 'namespace' res.objectType = 'namespace'
return res return res
@@ -3288,6 +3302,8 @@ class DefinitionParser(object):
def parse_xref_object(self): def parse_xref_object(self):
templatePrefix = self._parse_template_declaration_prefix() templatePrefix = self._parse_template_declaration_prefix()
name = self._parse_nested_name() name = self._parse_nested_name()
templatePrefix = self._check_template_consistency(name, templatePrefix,
fullSpecShorthand=True)
res = ASTNamespace(name, templatePrefix) res = ASTNamespace(name, templatePrefix)
res.objectType = 'xref' res.objectType = 'xref'
return res return res
@@ -3708,10 +3724,10 @@ class CPPDomain(Domain):
try: try:
ast = parser.parse_xref_object() ast = parser.parse_xref_object()
parser.skip_ws() parser.skip_ws()
if not parser.eof: parser.assert_end()
raise DefinitionError('') except DefinitionError as e:
except DefinitionError: warner.warn('Unparseable C++ cross-reference: %r\n%s'
warner.warn('Unparseable C++ cross-reference: %r' % target) % (target, str(e.description)))
return None, None return None, None
parentKey = node.get("cpp:parentKey", None) parentKey = node.get("cpp:parentKey", None)
rootSymbol = self.data['rootSymbol'] rootSymbol = self.data['rootSymbol']
@@ -3730,7 +3746,7 @@ class CPPDomain(Domain):
else: else:
templateDecls = [] templateDecls = []
s = parentSymbol.find_name(name, templateDecls, s = parentSymbol.find_name(name, templateDecls,
specific_specialisation=False) templateShorthand=True)
if s is None or s.declaration is None: if s is None or s.declaration is None:
return None, None return None, None
declaration = s.declaration declaration = s.declaration