diff --git a/CHANGES b/CHANGES index d9b28c7d3a7..647c3b1883e 100644 --- a/CHANGES +++ b/CHANGES @@ -167,13 +167,17 @@ Features added * #4785: napoleon: Add strings to translation file for localisation * #4927: Display a warning when invalid values are passed to linenothreshold option of highlight directive -* C++, add a ``cpp:texpr`` role as a sibling to ``cpp:expr``. -* C++, add support for unions. -* C++, add support for anonymous entities using names staring with ``@``. - Fixes #3593 and #2683. -* #5147: C++, add support for (most) character literals. -* C++, cross-referencing entities inside primary templates is supported, - and now properly documented. +* C++: + + - Add a ``cpp:texpr`` role as a sibling to ``cpp:expr``. + - Add support for unions. + - #3593, #2683: add support for anonymous entities using names staring with ``@``. + - #5147: add support for (most) character literals. + - Cross-referencing entities inside primary templates is supported, + and now properly documented. + - #1552: add new cross-referencing format for ``cpp:any`` and ``cpp:func`` roles, + for referencing specific function overloads. + * #3606: MathJax should be loaded with async attribute * html: Output ``canonical_url`` metadata if :confval:`html_baseurl` set (refs: #4193) diff --git a/doc/usage/restructuredtext/domains.rst b/doc/usage/restructuredtext/domains.rst index afc8e8d3977..e77075c1abf 100644 --- a/doc/usage/restructuredtext/domains.rst +++ b/doc/usage/restructuredtext/domains.rst @@ -1005,20 +1005,46 @@ These roles link to the given declaration types: When a custom title is not needed it may be useful to use the roles for inline expressions, :rst:role:`cpp:expr` and :rst:role:`cpp:texpr`, where angle brackets do not need escaping. -.. admonition:: Note on References to Overloaded Functions - - It is currently impossible to link to a specific version of an overloaded - function. Currently the C++ domain is the first domain that has basic - support for overloaded functions and until there is more data for comparison - we don't want to select a bad syntax to reference a specific overload. - Currently Sphinx will link to the first overloaded version of the 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``. + +Overloaded (member) functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When a (member) function is referenced using just its name, the reference +will point to an arbitrary matching overload. +The :rst:role:`cpp:any` and :rst:role:`cpp:func` roles will an alternative +format, which simply is a complete function declaration. +This will resolve to the exact matching overload. +As example, consider the following class declaration: + +.. cpp:namespace-push:: overload_example +.. cpp:class:: C + + .. cpp:function:: void f(double d) const + .. cpp:function:: void f(double d) + .. cpp:function:: void f(int i) + .. cpp:function:: void f() + +References using the :rst:role:`cpp:func` role: + +- Arbitrary overload: ``C::f``, :cpp:func:`C::f` +- Also arbitrary overload: ``C::f()``, :cpp:func:`C::f()` +- Specific overload: ``void C::f()``, :cpp:func:`void C::f()` +- Specific overload: ``void C::f(int)``, :cpp:func:`void C::f(int)` +- Specific overload: ``void C::f(double)``, :cpp:func:`void C::f(double)` +- Specific overload: ``void C::f(double) const``, :cpp:func:`void C::f(double) const` + +Note that the :confval:`add_function_parentheses` configuration variable +does not influence specific overload references. + +.. cpp:namespace-pop:: + + Templated declarations ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 5acba9d3b31..aeeaa2122de 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -622,20 +622,6 @@ def clone(self): """Clone a definition expression node.""" return deepcopy(self) - def get_name(self): - # type: () -> unicode - """Return the name. - - Returns either `None` or a node with a name you might call - :meth:`split_owner` on. - """ - raise NotImplementedError(repr(self)) - - def prefix_nested_name(self, prefix): - # type: (unicode) -> unicode - """Prefix a name node (a node returned by :meth:`get_name`).""" - raise NotImplementedError(repr(self)) - def _stringify(self, transform): # type: (Callable[[Any], unicode]) -> unicode raise NotImplementedError(repr(self)) @@ -1875,7 +1861,7 @@ def describe_signature(self, parentNode, mode, env, symbol, lineSpec): class ASTTemplateDeclarationPrefix(ASTBase): def __init__(self, templates): # type: (List[Any]) -> None - # template is None means it's an explicit instantiation of a variable + # templates is None means it's an explicit instantiation of a variable self.templates = templates def get_id(self, version): @@ -3606,6 +3592,16 @@ def __init__(self, nestedName, templatePrefix): self.templatePrefix = templatePrefix +class SymbolLookupResult(object): + def __init__(self, symbols, parentSymbol, identOrOp, templateParams, templateArgs): + # type: (Iterator[Symbol], Symbol, Union[ASTIdentifier, ASTOperator], Any, ASTTemplateArgs) -> None # NOQA + self.symbols = symbols + self.parentSymbol = parentSymbol + self.identOrOp = identOrOp + self.templateParams = templateParams + self.templateArgs = templateArgs + + class Symbol(object): def _assert_invariants(self): # type: () -> None @@ -3644,6 +3640,7 @@ def __init__(self, self.isRedeclaration = False self._assert_invariants() + # Remember to modify Symbol.remove if modifications to the parent change. self._children = [] # type: List[Symbol] self._anonChildren = [] # type: List[Symbol] # note: _children includes _anonChildren @@ -3682,6 +3679,13 @@ def __init__(self, templateParams=None, templateArgs=None, declaration=decl, docname=docname) + def remove(self): + if self.parent is None: + return + assert self in self.parent._children + self.parent._children.remove(self) + self.parent = None + def _fill_empty(self, declaration, docname): # type: (ASTDeclaration, unicode) -> None self._assert_invariants() @@ -3745,16 +3749,35 @@ def get_full_nested_name(self): templates.append(False) return ASTNestedName(names, templates, rooted=False) - def _find_named_symbol(self, - identOrOp, # type: Union[ASTIdentifier, ASTOperator] - templateParams, # type: Any - templateArgs, # type: ASTTemplateArgs - templateShorthand, # type: bool - matchSelf, # type: bool - recurseInAnon, # type: bool - correctPrimaryTemplateArgs # type: bool - ): + def _find_first_named_symbol( + self, + identOrOp, # type: Union[ASTIdentifier, ASTOperator] + templateParams, # type: Any + templateArgs, # type: ASTTemplateArgs + templateShorthand, # type: bool + matchSelf, # type: bool + recurseInAnon, # type: bool + correctPrimaryTemplateArgs # type: bool + ): # NOQA # type: (...) -> Symbol + res = self._find_named_symbols(identOrOp, templateParams, templateArgs, + templateShorthand, matchSelf, recurseInAnon, + correctPrimaryTemplateArgs) + try: + return next(res) + except StopIteration: + return None + + def _find_named_symbols(self, + identOrOp, # type: Union[ASTIdentifier, ASTOperator] + templateParams, # type: Any + templateArgs, # type: ASTTemplateArgs + templateShorthand, # type: bool + matchSelf, # type: bool + recurseInAnon, # type: bool + correctPrimaryTemplateArgs # type: bool + ): + # type: (...) -> Iterator[Symbol] def isSpecialization(): # the names of the template parameters must be given exactly as args @@ -3807,12 +3830,11 @@ def matches(s): return False return True if matchSelf and matches(self): - return self + yield self children = self.children_recurse_anon if recurseInAnon else self._children for s in children: if matches(s): - return s - return None + yield s def _symbol_lookup( self, @@ -3827,7 +3849,7 @@ def _symbol_lookup( recurseInAnon, # type: bool correctPrimaryTemplateArgs # type: bool ): - # type: (...) -> Tuple[Symbol, Symbol, Union[ASTIdentifier, ASTOperator], Any, ASTTemplateArgs] # NOQA + # type: (...) -> SymbolLookupResult # ancestorLookupType: if not None, specifies the target type of the lookup if strictTemplateParamArgLists: @@ -3885,7 +3907,7 @@ def _symbol_lookup( else: templateParams = None - symbol = parentSymbol._find_named_symbol( + symbol = parentSymbol._find_first_named_symbol( identOrOp, templateParams, templateArgs, templateShorthand=templateShorthand, @@ -3896,7 +3918,7 @@ def _symbol_lookup( symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateArgs) if symbol is None: - return None, None, None, None, None + return None # We have now matched part of a nested name, and need to match more # so even if we should matchSelf before, we definitely shouldn't # even more. (see also issue #2666) @@ -3914,13 +3936,12 @@ def _symbol_lookup( assert iTemplateDecl == len(templateDecls) templateParams = None - symbol = parentSymbol._find_named_symbol(identOrOp, - templateParams, templateArgs, - templateShorthand=templateShorthand, - matchSelf=matchSelf, - recurseInAnon=recurseInAnon, - correctPrimaryTemplateArgs=False) - return symbol, parentSymbol, identOrOp, templateParams, templateArgs + symbols = parentSymbol._find_named_symbols( + identOrOp, templateParams, templateArgs, + templateShorthand=templateShorthand, matchSelf=matchSelf, + recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False) + return SymbolLookupResult(symbols, parentSymbol, + identOrOp, templateParams, templateArgs) def _add_symbols(self, nestedName, templateDecls, declaration, docname): # type: (ASTNestedName, List[Any], ASTDeclaration, unicode) -> Symbol @@ -3934,15 +3955,20 @@ def onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateAr templateArgs=templateArgs, declaration=None, docname=None) - symbol, parentSymbol, identOrOp, templateParams, templateArgs = \ - self._symbol_lookup(nestedName, templateDecls, - onMissingQualifiedSymbol, - strictTemplateParamArgLists=True, - ancestorLookupType=None, - templateShorthand=False, - matchSelf=False, - recurseInAnon=True, - correctPrimaryTemplateArgs=True) + lookupResult = self._symbol_lookup(nestedName, templateDecls, + onMissingQualifiedSymbol, + strictTemplateParamArgLists=True, + ancestorLookupType=None, + templateShorthand=False, + matchSelf=False, + recurseInAnon=True, + correctPrimaryTemplateArgs=True) + assert lookupResult is not None # we create symbols all the way, so that can't happen + # TODO: actually do the iteration over results, though let's find a test case first + try: + symbol = next(lookupResult.symbols) + except StopIteration: + symbol = None if symbol: if not declaration: @@ -3958,9 +3984,10 @@ def onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateAr return symbol # It may simply be a function overload, so let's compare ids. isRedeclaration = True - candSymbol = Symbol(parent=parentSymbol, identOrOp=identOrOp, - templateParams=templateParams, - templateArgs=templateArgs, + candSymbol = Symbol(parent=lookupResult.parentSymbol, + identOrOp=lookupResult.identOrOp, + templateParams=lookupResult.templateParams, + templateArgs=lookupResult.templateArgs, declaration=declaration, docname=docname) if declaration.objectType == "function": @@ -3978,9 +4005,10 @@ def onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateAr candSymbol.isRedeclaration = True raise _DuplicateSymbolError(symbol, declaration) else: - symbol = Symbol(parent=parentSymbol, identOrOp=identOrOp, - templateParams=templateParams, - templateArgs=templateArgs, + symbol = Symbol(parent=lookupResult.parentSymbol, + identOrOp=lookupResult.identOrOp, + templateParams=lookupResult.templateParams, + templateArgs=lookupResult.templateArgs, declaration=declaration, docname=docname) return symbol @@ -3989,13 +4017,12 @@ def merge_with(self, other, docnames, env): # type: (Symbol, List[unicode], BuildEnvironment) -> None assert other is not None for otherChild in other._children: - ourChild = self._find_named_symbol(identOrOp=otherChild.identOrOp, - templateParams=otherChild.templateParams, - templateArgs=otherChild.templateArgs, - templateShorthand=False, - matchSelf=False, - recurseInAnon=False, - correctPrimaryTemplateArgs=False) + ourChild = self._find_first_named_symbol( + identOrOp=otherChild.identOrOp, + templateParams=otherChild.templateParams, + templateArgs=otherChild.templateArgs, + templateShorthand=False, matchSelf=False, + recurseInAnon=False, correctPrimaryTemplateArgs=False) if ourChild is None: # TODO: hmm, should we prune by docnames? self._children.append(otherChild) @@ -4054,12 +4081,12 @@ def direct_lookup(self, key): for name, templateParams in key: identOrOp = name.identOrOp templateArgs = name.templateArgs - s = s._find_named_symbol(identOrOp, - templateParams, templateArgs, - templateShorthand=False, - matchSelf=False, - recurseInAnon=False, - correctPrimaryTemplateArgs=False) + s = s._find_first_named_symbol(identOrOp, + templateParams, templateArgs, + templateShorthand=False, + matchSelf=False, + recurseInAnon=False, + correctPrimaryTemplateArgs=False) if not s: return None return s @@ -4077,30 +4104,76 @@ def onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateAr # Is there another case where it would be good? return None - symbol, parentSymbol, identOrOp, templateParams, templateArgs = \ - self._symbol_lookup(nestedName, templateDecls, - onMissingQualifiedSymbol, - strictTemplateParamArgLists=False, - ancestorLookupType=typ, - templateShorthand=templateShorthand, - matchSelf=matchSelf, - recurseInAnon=recurseInAnon, - correctPrimaryTemplateArgs=False) - - if symbol is not None: - return symbol - # if it was a part of the qualification that could not be found: - if parentSymbol is None: + lookupResult = self._symbol_lookup(nestedName, templateDecls, + onMissingQualifiedSymbol, + strictTemplateParamArgLists=False, + ancestorLookupType=typ, + templateShorthand=templateShorthand, + matchSelf=matchSelf, + recurseInAnon=recurseInAnon, + correctPrimaryTemplateArgs=False) + if lookupResult is None: + # if it was a part of the qualification that could not be found return None + + # TODO: hmm, what if multiple symbols match? + try: + return next(lookupResult.symbols) + except StopIteration: + pass + # try without template params and args - symbol = parentSymbol._find_named_symbol(identOrOp, - None, None, - templateShorthand=templateShorthand, - matchSelf=matchSelf, - recurseInAnon=recurseInAnon, - correctPrimaryTemplateArgs=False) + symbol = lookupResult.parentSymbol._find_first_named_symbol( + lookupResult.identOrOp, None, None, + templateShorthand=templateShorthand, matchSelf=matchSelf, + recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False) return symbol + def find_declaration(self, declaration, typ, templateShorthand, + matchSelf, recurseInAnon): + # type: (ASTDeclaration, unicode, bool, bool, bool) -> Symbol + # templateShorthand: missing template parameter lists for templates is ok + nestedName = declaration.name + if declaration.templatePrefix: + templateDecls = declaration.templatePrefix.templates + else: + templateDecls = [] + + def onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateArgs): + # type: (Symbol, Union[ASTIdentifier, ASTOperator], Any, ASTTemplateArgs) -> Symbol + return None + + lookupResult = self._symbol_lookup(nestedName, templateDecls, + onMissingQualifiedSymbol, + strictTemplateParamArgLists=False, + ancestorLookupType=typ, + templateShorthand=templateShorthand, + matchSelf=matchSelf, + recurseInAnon=recurseInAnon, + correctPrimaryTemplateArgs=False) + + if lookupResult is None: + return None + + symbols = list(lookupResult.symbols) + if len(symbols) == 0: + return None + + querySymbol = Symbol(parent=lookupResult.parentSymbol, + identOrOp=lookupResult.identOrOp, + templateParams=lookupResult.templateParams, + templateArgs=lookupResult.templateArgs, + declaration=declaration, + docname='fakeDocnameForQuery') + queryId = declaration.get_newest_id() + for symbol in symbols: + candId = symbol.declaration.get_newest_id() + if candId == queryId: + querySymbol.remove() + return symbol + querySymbol.remove() + return None + def to_string(self, indent): # type: (int) -> unicode res = ['\t' * indent] # type: List[unicode] @@ -5825,9 +5898,13 @@ def _parse_template_declaration_prefix(self, objectType): else: return ASTTemplateDeclarationPrefix(templates) - def _check_template_consistency(self, nestedName, templatePrefix, - fullSpecShorthand, isMember=False): - # type: (Any, Any, Any, bool) -> ASTTemplateDeclarationPrefix + def _check_template_consistency(self, + nestedName, # type: ASTNestedName + templatePrefix, # type: ASTTemplateDeclarationPrefix + fullSpecShorthand, # type: bool + isMember=False # type: bool + ): + # type: (...) -> ASTTemplateDeclarationPrefix numArgs = nestedName.num_templates() isMemberInstantiation = False if not templatePrefix: @@ -5931,17 +6008,33 @@ def parse_namespace_object(self): return res def parse_xref_object(self): - # type: () -> ASTNamespace - templatePrefix = self._parse_template_declaration_prefix(objectType="xref") - name = self._parse_nested_name() - # if there are '()' left, just skip them - self.skip_ws() - self.skip_string('()') - templatePrefix = self._check_template_consistency(name, templatePrefix, - fullSpecShorthand=True) - res = ASTNamespace(name, templatePrefix) - res.objectType = 'xref' # type: ignore - return res + # type: () -> Tuple[Any, bool] + pos = self.pos + try: + templatePrefix = self._parse_template_declaration_prefix(objectType="xref") + name = self._parse_nested_name() + # if there are '()' left, just skip them + self.skip_ws() + self.skip_string('()') + templatePrefix = self._check_template_consistency(name, templatePrefix, + fullSpecShorthand=True) + res1 = ASTNamespace(name, templatePrefix) + res1.objectType = 'xref' # type: ignore + return res1, True + except DefinitionError as e1: + try: + self.pos = pos + res2 = self.parse_declaration('function') + # if there are '()' left, just skip them + self.skip_ws() + self.skip_string('()') + return res2, False + except DefinitionError as e2: + errs = [] + errs.append((e1, "If shorthand ref")) + errs.append((e2, "If full function ref")) + msg = "Error in cross-reference." + raise self._make_multi_error(errs, msg) def parse_expression(self): pos = self.pos @@ -6542,7 +6635,7 @@ def warn(self, msg): target += '()' parser = DefinitionParser(target, warner, env.config) try: - ast = parser.parse_xref_object() + ast, isShorthand = parser.parse_xref_object() parser.assert_end() except DefinitionError as e: def findWarning(e): # as arg to stop flake8 from complaining @@ -6573,14 +6666,21 @@ def findWarning(e): # as arg to stop flake8 from complaining else: parentSymbol = rootSymbol - name = ast.nestedName - if ast.templatePrefix: - templateDecls = ast.templatePrefix.templates + if isShorthand: + ns = ast # type: ASTNamespace + name = ns.nestedName + if ns.templatePrefix: + templateDecls = ns.templatePrefix.templates + else: + templateDecls = [] + s = parentSymbol.find_name(name, templateDecls, typ, + templateShorthand=True, + matchSelf=True, recurseInAnon=True) else: - templateDecls = [] - s = parentSymbol.find_name(name, templateDecls, typ, - templateShorthand=True, - matchSelf=True, recurseInAnon=True) + decl = ast # type: ASTDeclaration + s = parentSymbol.find_declaration(decl, typ, + templateShorthand=True, + matchSelf=True, recurseInAnon=True) if s is None or s.declaration is None: txtName = text_type(name) if txtName.startswith('std::') or txtName == 'std': @@ -6609,8 +6709,11 @@ def checkType(): s.get_full_nested_name())) declaration = s.declaration - fullNestedName = s.get_full_nested_name() - displayName = fullNestedName.get_display_string().lstrip(':') + if isShorthand: + fullNestedName = s.get_full_nested_name() + displayName = fullNestedName.get_display_string().lstrip(':') + else: + displayName = decl.get_display_string() docname = s.docname assert docname @@ -6622,19 +6725,33 @@ def checkType(): # are requested. Then the Sphinx machinery will add another pair. # Also, if it's an 'any' ref that resolves to a function, we need to add # parens as well. + # However, if it's a non-shorthand function ref, for a function that + # takes no arguments, then we may need to add parens again as well. addParen = 0 if not node.get('refexplicit', False) and declaration.objectType == 'function': - # this is just the normal haxing for 'any' roles - if env.config.add_function_parentheses and typ == 'any': - addParen += 1 - # and now this stuff for operator() - if (env.config.add_function_parentheses and typ == 'function' and - title.endswith('operator()')): - addParen += 1 - if ((typ == 'any' or typ == 'function') and - title.endswith('operator') and - displayName.endswith('operator()')): - addParen += 1 + if isShorthand: + # this is just the normal haxing for 'any' roles + if env.config.add_function_parentheses and typ == 'any': + addParen += 1 + # and now this stuff for operator() + if (env.config.add_function_parentheses and typ == 'function' and + title.endswith('operator()')): + addParen += 1 + if ((typ == 'any' or typ == 'function') and + title.endswith('operator') and + displayName.endswith('operator()')): + addParen += 1 + else: + # our job here is to essentially nullify add_function_parentheses + if env.config.add_function_parentheses: + if typ == 'any' and displayName.endswith('()'): + addParen += 1 + elif typ == 'function': + if title.endswith('()') and not displayName.endswith('()'): + title = title[:-2] + else: + if displayName.endswith('()'): + addParen += 1 if addParen > 0: title += '()' * addParen # and reconstruct the title again