diff --git a/CHANGES b/CHANGES index 1f1f5ebe5..a41ec6724 100644 --- a/CHANGES +++ b/CHANGES @@ -43,6 +43,8 @@ Features added Bugs fixed ---------- +* C++, fix cross reference lookup in certain cases involving function overloads. + Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 08951ff3f..47a9d3906 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -367,6 +367,8 @@ _keywords = [ _max_id = 4 _id_prefix = [None, '', '_CPPv2', '_CPPv3', '_CPPv4'] +# Ids are used in lookup keys which are used across pickled files, +# so when _max_id changes, make sure to update the ENV_VERSION. # ------------------------------------------------------------------------------ # Id v1 constants @@ -1790,7 +1792,8 @@ class ASTTemplateIntroduction(ASTBase): class ASTTemplateDeclarationPrefix(ASTBase): - def __init__(self, templates: List[Any]) -> None: + def __init__(self, templates: List[Union[ASTTemplateParams, ASTTemplateIntroduction]])\ + -> None: # templates is None means it's an explicit instantiation of a variable self.templates = templates @@ -3547,6 +3550,14 @@ class SymbolLookupResult: self.templateArgs = templateArgs +class LookupKey: + def __init__(self, data: List[Tuple[ASTNestedNameElement, + Union[ASTTemplateParams, + ASTTemplateIntroduction], + str]]) -> None: + self.data = data + + class Symbol: debug_lookup = False debug_show_tree = False @@ -3570,8 +3581,8 @@ class Symbol: return super().__setattr__(key, value) def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], - templateParams: Any, templateArgs: Any, declaration: ASTDeclaration, - docname: str) -> None: + templateParams: Union[ASTTemplateParams, ASTTemplateIntroduction], + templateArgs: Any, declaration: ASTDeclaration, docname: str) -> None: self.parent = parent self.identOrOp = identOrOp self.templateParams = templateParams # template @@ -3669,7 +3680,11 @@ class Symbol: yield from c.children_recurse_anon - def get_lookup_key(self) -> List[Tuple[ASTNestedNameElement, Any]]: + def get_lookup_key(self)-> "LookupKey": + # The pickle files for the environment and for each document are distinct. + # The environment has all the symbols, but the documents has xrefs that + # must know their scope. A lookup key is essentially a specification of + # how to find a specific symbol. symbols = [] s = self while s.parent: @@ -3679,14 +3694,23 @@ class Symbol: key = [] for s in symbols: nne = ASTNestedNameElement(s.identOrOp, s.templateArgs) - key.append((nne, s.templateParams)) - return key + if s.declaration is not None: + key.append((nne, s.templateParams, s.declaration.get_newest_id())) + else: + key.append((nne, s.templateParams, None)) + return LookupKey(key) def get_full_nested_name(self) -> ASTNestedName: + symbols = [] + s = self + while s.parent: + symbols.append(s) + s = s.parent + symbols.reverse() names = [] templates = [] - for nne, templateParams in self.get_lookup_key(): - names.append(nne) + for s in symbols: + names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs)) templates.append(False) return ASTNestedName(names, templates, rooted=False) @@ -4082,16 +4106,26 @@ class Symbol: def direct_lookup(self, key: List[Tuple[ASTNestedNameElement, Any]]) -> "Symbol": s = self - for name, templateParams in key: - identOrOp = name.identOrOp - templateArgs = name.templateArgs - s = s._find_first_named_symbol(identOrOp, - templateParams, templateArgs, - templateShorthand=False, - matchSelf=False, - recurseInAnon=False, - correctPrimaryTemplateArgs=False) - if not s: + for name, templateParams, id_ in key.data: + if id_ is not None: + res = None + for cand in s._children: + if cand.declaration is None: + continue + if cand.declaration.get_newest_id() == id_: + res = cand + break + s = res + else: + identOrOp = name.identOrOp + templateArgs = name.templateArgs + s = s._find_first_named_symbol(identOrOp, + templateParams, templateArgs, + templateShorthand=False, + matchSelf=False, + recurseInAnon=False, + correctPrimaryTemplateArgs=False) + if s is None: return None return s @@ -5931,14 +5965,15 @@ class DefinitionParser: def _parse_template_declaration_prefix(self, objectType: str ) -> ASTTemplateDeclarationPrefix: - templates = [] # type: List[str] + templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] while 1: self.skip_ws() # the saved position is only used to provide a better error message + params = None # type: Union[ASTTemplateParams, ASTTemplateIntroduction] pos = self.pos if self.skip_word("template"): try: - params = self._parse_template_parameter_list() # type: Any + params = self._parse_template_parameter_list() except DefinitionError as e: if objectType == 'member' and len(templates) == 0: return ASTTemplateDeclarationPrefix(None) @@ -5990,7 +6025,7 @@ class DefinitionParser: msg += str(nestedName) self.warn(msg) - newTemplates = [] + newTemplates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]] for i in range(numExtra): newTemplates.append(ASTTemplateParams([])) if templatePrefix and not isMemberInstantiation: @@ -6329,10 +6364,10 @@ class CPPObject(ObjectDescription): return ast def before_content(self) -> None: - lastSymbol = self.env.temp_data['cpp:last_symbol'] + lastSymbol = self.env.temp_data['cpp:last_symbol'] # type: Symbol assert lastSymbol self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol'] - self.oldParentKey = self.env.ref_context['cpp:parent_key'] + self.oldParentKey = self.env.ref_context['cpp:parent_key'] # type: LookupKey self.env.temp_data['cpp:parent_symbol'] = lastSymbol self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key() @@ -6824,13 +6859,13 @@ class CPPDomain(Domain): t, ex = findWarning(e) warner.warn('Unparseable C++ cross-reference: %r\n%s' % (t, ex)) return None, None - parentKey = node.get("cpp:parent_key", None) + parentKey = node.get("cpp:parent_key", None) # type: LookupKey rootSymbol = self.data['root_symbol'] if parentKey: - parentSymbol = rootSymbol.direct_lookup(parentKey) + parentSymbol = rootSymbol.direct_lookup(parentKey) # type: Symbol if not parentSymbol: print("Target: ", target) - print("ParentKey: ", parentKey) + print("ParentKey: ", parentKey.data) print(rootSymbol.dump(1)) assert parentSymbol # should be there else: @@ -6977,8 +7012,8 @@ class CPPDomain(Domain): target = node.get('reftarget', None) if target is None: return None - parentKey = node.get("cpp:parent_key", None) - if parentKey is None or len(parentKey) <= 0: + parentKey = node.get("cpp:parent_key", None) # type: LookupKey + if parentKey is None or len(parentKey.data) <= 0: return None rootSymbol = self.data['root_symbol'] diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 341f22a27..f807574d6 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -61,7 +61,7 @@ default_settings = { # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. -ENV_VERSION = 56 +ENV_VERSION = 57 # config status CONFIG_OK = 1 diff --git a/tests/roots/test-domain-cpp/lookup-key-overload.rst b/tests/roots/test-domain-cpp/lookup-key-overload.rst new file mode 100644 index 000000000..2011e26d6 --- /dev/null +++ b/tests/roots/test-domain-cpp/lookup-key-overload.rst @@ -0,0 +1,8 @@ +.. default-domain:: cpp + +.. namespace:: lookup_key_overload + +.. function:: void g(int a) +.. function:: void g(double b) + + :var:`b` diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index b2cbf3035..b1796cdff 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -796,6 +796,13 @@ def filter_warnings(warning, file): return res +@pytest.mark.sphinx(testroot='domain-cpp') +def test_build_domain_cpp_multi_decl_lookup(app, status, warning): + app.builder.build_all() + ws = filter_warnings(warning, "lookup-key-overload") + assert len(ws) == 0 + + @pytest.mark.sphinx(testroot='domain-cpp') def test_build_domain_cpp_misuse_of_roles(app, status, warning): app.builder.build_all()