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
~~~~~~~~~~~~~~~~~
These roles link to the given object types:
These roles link to the given declaration types:
.. rst:role:: cpp:any
cpp:class
@ -775,19 +775,19 @@ These roles link to the given object types:
cpp:enum
cpp:enumerator
Reference a C++ object by name. The name must be properly qualified relative to the
position of the link.
Reference a C++ declaration by name (see below for details).
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
linking to template classes, if nothing follows the closing angle
bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<T>```.
This is interpreted as a link to ``T`` with a title of ``MyClass``.
In this case, please escape the opening angle bracket with a backslash,
like this: ``:cpp:class:`MyClass\<T>```.
Sphinx's syntax to give references a custom title can interfere with
linking to template classes, if nothing follows the closing angle
bracket, i.e. if the link looks like this: ``:cpp:class:`MyClass<int>```.
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,
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
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
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
-------------------

View File

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