C++, fix hyperlinking of nested names

This commit is contained in:
Jakob Lykke Andersen 2017-11-30 23:06:30 +01:00
parent eff13bc4c4
commit 0ff217e1b2
3 changed files with 87 additions and 30 deletions

View File

@ -83,6 +83,7 @@ Bugs fixed
* #3882: Update the order of files for HTMLHelp and QTHelp
* #3962: sphinx-apidoc does not recognize implicit namespace packages correctly
* C++: also hyperlink types in the name of declarations with qualified names.
Testing
--------

View File

@ -555,12 +555,12 @@ class DefinitionError(UnicodeMixin, Exception):
class _DuplicateSymbolError(UnicodeMixin, Exception):
def __init__(self, symbol, candSymbol):
# type: (Symbol, Symbol) -> None
def __init__(self, symbol, declaration):
# type: (Symbol, Any) -> None
assert symbol
assert candSymbol
assert declaration
self.symbol = symbol
self.candSymbol = candSymbol
self.declaration = declaration
def __unicode__(self):
# type: () -> unicode
@ -1286,6 +1286,10 @@ class ASTTemplateParamType(ASTBase):
id = self.get_identifier()
return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False)
@property
def isPack(self):
return self.data.parameterPack
def get_identifier(self):
# type: () -> unicode
return self.data.get_identifier()
@ -1447,6 +1451,16 @@ class ASTTemplateIntroductionParameter(ASTBase):
self.identifier = identifier
self.parameterPack = parameterPack
@property
def name(self):
# type: () -> ASTNestedName
id = self.get_identifier()
return ASTNestedName([ASTNestedNameElement(id, None)], rooted=False)
@property
def isPack(self):
return self.parameterPack
def get_identifier(self):
# type: () -> unicode
return self.identifier
@ -1819,36 +1833,42 @@ class ASTNestedName(ASTBase):
# type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
_verify_description_mode(mode)
# just print the name part, with template args, not template params
if mode == 'lastIsName':
addname = [] # type: List[unicode]
if self.rooted:
addname.append('')
for n in self.names[:-1]:
addname.append(text_type(n))
addname = '::'.join(addname) # type: ignore
if len(self.names) > 1:
addname += '::'
signode += addnodes.desc_addname(addname, addname)
self.names[-1].describe_signature(signode, mode, env, '', symbol)
elif mode == 'noneIsName':
if mode == 'noneIsName':
signode += nodes.Text(text_type(self))
elif mode == 'param':
name = text_type(self)
signode += nodes.emphasis(name, name)
elif mode == 'markType':
# each element should be a pending xref targeting the complete
elif mode == 'markType' or mode == 'lastIsName':
# Each element should be a pending xref targeting the complete
# prefix. however, only the identifier part should be a link, such
# that template args can be a link as well.
# For 'lastIsName' we should also prepend template parameter lists.
templateParams = [] # type: List[Any]
if mode == 'lastIsName':
assert symbol is not None
if symbol.declaration.templatePrefix is not None:
templateParams = symbol.declaration.templatePrefix.templates
iTemplateParams = 0
templateParamsPrefix = u''
prefix = '' # type: unicode
first = True
for name in self.names:
names = self.names[:-1] if mode == 'lastIsName' else self.names
for name in names:
if not first:
signode += nodes.Text('::')
prefix += '::'
first = False
if name != '':
name.describe_signature(signode, mode, env, prefix, symbol) # type: ignore
if name.templateArgs and iTemplateParams < len(templateParams): # type: ignore
templateParamsPrefix += text_type(templateParams[iTemplateParams])
iTemplateParams += 1
name.describe_signature(signode, 'markType', # type: ignore
env, templateParamsPrefix + prefix, symbol)
prefix += text_type(name)
if mode == 'lastIsName':
if len(self.names) > 1:
signode += addnodes.desc_addname('::', '::')
self.names[-1].describe_signature(signode, mode, env, '', symbol)
else:
raise Exception('Unknown description mode: %s' % mode)
@ -3131,15 +3151,15 @@ class ASTDeclaration(ASTBase):
def describe_signature(self, signode, mode, env, options):
# type: (addnodes.desc_signature, unicode, BuildEnvironment, Dict) -> None
_verify_description_mode(mode)
assert self.symbol
# The caller of the domain added a desc_signature node.
# Always enable multiline:
signode['is_multiline'] = True
# Put each line in a desc_signature_line node.
mainDeclNode = addnodes.desc_signature_line()
mainDeclNode.sphinx_cpp_tagname = 'declarator'
mainDeclNode['add_permalink'] = True
mainDeclNode['add_permalink'] = not self.symbol.isRedeclaration
assert self.symbol
if self.templatePrefix:
self.templatePrefix.describe_signature(signode, mode, env,
symbol=self.symbol,
@ -3206,6 +3226,7 @@ class Symbol(object):
self.templateArgs = templateArgs # identifier<templateArgs>
self.declaration = declaration
self.docname = docname
self.isRedeclaration = False
self._assert_invariants()
self.children = [] # type: List[Any]
@ -3294,6 +3315,31 @@ class Symbol(object):
# type: (Any, Any, Any, Any, Any, bool) -> Symbol
assert (identifier is None) != (operator is None)
def isSpecialization():
# the names of the template parameters must be given exactly as args
# and params that are packs must in the args be the name expanded
if len(templateParams.params) != len(templateArgs.args):
return True
for i in range(len(templateParams.params)):
param = templateParams.params[i]
arg = templateArgs.args[i]
# TODO: doing this by string manipulation is probably not the most efficient
paramName = text_type(param.name)
argTxt = text_type(arg)
isArgPackExpansion = argTxt.endswith('...')
if param.isPack != isArgPackExpansion:
return True
argName = argTxt[:-3] if isArgPackExpansion else argTxt
if paramName != argName:
return True
return False
if templateParams is not None and templateArgs is not None:
# If both are given, but it's not a specialization, then do lookup as if
# there is no argument list.
# For example: template<typename T> int A<T>::var;
if not isSpecialization():
templateArgs = None
def matches(s):
if s.identifier != identifier:
return False
@ -3405,23 +3451,27 @@ class Symbol(object):
# .. class:: Test
symbol._fill_empty(declaration, docname)
return symbol
# It may simply be a functin overload, so let's compare ids.
# It may simply be a function overload, so let's compare ids.
isRedeclaration = True
candSymbol = Symbol(parent=parentSymbol, identifier=identifier,
templateParams=templateParams,
templateArgs=templateArgs,
declaration=declaration,
docname=docname)
newId = declaration.get_newest_id()
oldId = symbol.declaration.get_newest_id()
if newId != oldId:
# we already inserted the symbol, so return the new one
symbol = candSymbol
else:
if declaration.objectType == "function":
newId = declaration.get_newest_id()
oldId = symbol.declaration.get_newest_id()
if newId != oldId:
# we already inserted the symbol, so return the new one
symbol = candSymbol
isRedeclaration = False
if isRedeclaration:
# Redeclaration of the same symbol.
# Let the new one be there, but raise an error to the client
# so it can use the real symbol as subscope.
# This will probably result in a duplicate id warning.
raise _DuplicateSymbolError(symbol, candSymbol)
candSymbol.isRedeclaration = True
raise _DuplicateSymbolError(symbol, declaration)
else:
symbol = Symbol(parent=parentSymbol, identifier=identifier,
templateParams=templateParams,
@ -5397,6 +5447,7 @@ class CPPObject(ObjectDescription):
# Assume we are actually in the old symbol,
# instead of the newly created duplicate.
self.env.temp_data['cpp:last_symbol'] = e.symbol
self.warn("Duplicate declaration.")
if ast.objectType == 'enumerator':
self._add_enumerator_to_parent(ast)
@ -5738,6 +5789,7 @@ class CPPDomain(Domain):
def _resolve_xref_inner(self, env, fromdocname, builder, typ,
target, node, contnode, emitWarnings=True):
# type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node, bool) -> nodes.Node # NOQA
class Warner(object):
def warn(self, msg):
if emitWarnings:

View File

@ -508,6 +508,10 @@ def test_templates():
check('class', "template<typename T = Test> A", {2:"I0E1A"})
check('class', "template<template<typename> typename T> A", {2:"II0E0E1A"})
check('class', "template<template<typename> typename> A", {2: "II0E0E1A"})
check('class', "template<template<typename> typename ...T> A", {2:"II0EDpE1A"})
check('class', "template<template<typename> typename...> A", {2: "II0EDpE1A"})
check('class', "template<int> A", {2:"I_iE1A"})
check('class', "template<int T> A", {2:"I_iE1A"})
check('class', "template<int... T> A", {2:"I_DpiE1A"})