diff --git a/CHANGES b/CHANGES index ca79187f7aa..e39315491c3 100644 --- a/CHANGES +++ b/CHANGES @@ -97,6 +97,9 @@ Deprecated Features added -------------- +- C and C++, show line numbers for previous declarations when duplicates are + detected. + Bugs fixed ---------- diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 7652761b548..cb99de9e1b6 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -227,6 +227,7 @@ def check(docname: str) -> Tuple[str, str, int]: if rex.match(uri): return 'ignored', '', 0 else: + self.broken[uri] = '' return 'broken', '', 0 elif uri in self.good: return 'working', 'old', 0 diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index fafe827af99..e3570b593f6 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -1475,7 +1475,7 @@ def __deepcopy__(self, memo): assert False # shouldn't happen else: # the domain base class makes a copy of the initial data, which is fine - return Symbol(None, None, None, None) + return Symbol(None, None, None, None, None) @staticmethod def debug_print(*args: Any) -> None: @@ -1498,7 +1498,7 @@ def __setattr__(self, key: str, value: Any) -> None: return super().__setattr__(key, value) def __init__(self, parent: "Symbol", ident: ASTIdentifier, - declaration: ASTDeclaration, docname: str) -> None: + declaration: ASTDeclaration, docname: str, line: int) -> None: self.parent = parent # declarations in a single directive are linked together self.siblingAbove = None # type: Symbol @@ -1506,6 +1506,7 @@ def __init__(self, parent: "Symbol", ident: ASTIdentifier, self.ident = ident self.declaration = declaration self.docname = docname + self.line = line self.isRedeclaration = False self._assert_invariants() @@ -1521,12 +1522,14 @@ def __init__(self, parent: "Symbol", ident: ASTIdentifier, # Do symbol addition after self._children has been initialised. self._add_function_params() - def _fill_empty(self, declaration: ASTDeclaration, docname: str) -> None: + def _fill_empty(self, declaration: ASTDeclaration, docname: str, line: int) -> None: self._assert_invariants() - assert not self.declaration - assert not self.docname - assert declaration - assert docname + assert self.declaration is None + assert self.docname is None + assert self.line is None + assert declaration is not None + assert docname is not None + assert line is not None self.declaration = declaration self.declaration.symbol = self self.docname = docname @@ -1553,7 +1556,7 @@ def _add_function_params(self) -> None: decl = ASTDeclaration('functionParam', None, p) assert not nn.rooted assert len(nn.names) == 1 - self._add_symbols(nn, decl, self.docname) + self._add_symbols(nn, decl, self.docname, self.line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 @@ -1570,6 +1573,7 @@ def clear_doc(self, docname: str) -> None: if sChild.declaration and sChild.docname == docname: sChild.declaration = None sChild.docname = None + sChild.line = None if sChild.siblingAbove is not None: sChild.siblingAbove.siblingBelow = sChild.siblingBelow if sChild.siblingBelow is not None: @@ -1763,7 +1767,7 @@ def _symbol_lookup(self, nestedName: ASTNestedName, return SymbolLookupResult(symbols, parentSymbol, ident) def _add_symbols(self, nestedName: ASTNestedName, - declaration: ASTDeclaration, docname: str) -> "Symbol": + declaration: ASTDeclaration, docname: str, line: int) -> "Symbol": # TODO: further simplification from C++ to C # Used for adding a whole path of symbols, where the last may or may not # be an actual declaration. @@ -1772,9 +1776,9 @@ def _add_symbols(self, nestedName: ASTNestedName, Symbol.debug_indent += 1 Symbol.debug_print("_add_symbols:") Symbol.debug_indent += 1 - Symbol.debug_print("nn: ", nestedName) - Symbol.debug_print("decl: ", declaration) - Symbol.debug_print("doc: ", docname) + Symbol.debug_print("nn: ", nestedName) + Symbol.debug_print("decl: ", declaration) + Symbol.debug_print("location: {}:{}".format(docname, line)) def onMissingQualifiedSymbol(parentSymbol: "Symbol", ident: ASTIdentifier) -> "Symbol": if Symbol.debug_lookup: @@ -1784,7 +1788,7 @@ def onMissingQualifiedSymbol(parentSymbol: "Symbol", ident: ASTIdentifier) -> "S Symbol.debug_print("ident: ", ident) Symbol.debug_indent -= 2 return Symbol(parent=parentSymbol, ident=ident, - declaration=None, docname=None) + declaration=None, docname=None, line=None) lookupResult = self._symbol_lookup(nestedName, onMissingQualifiedSymbol, @@ -1800,12 +1804,12 @@ def onMissingQualifiedSymbol(parentSymbol: "Symbol", ident: ASTIdentifier) -> "S Symbol.debug_indent += 1 Symbol.debug_print("ident: ", lookupResult.ident) Symbol.debug_print("declaration: ", declaration) - Symbol.debug_print("docname: ", docname) + Symbol.debug_print("location: {}:{}".format(docname, line)) Symbol.debug_indent -= 1 symbol = Symbol(parent=lookupResult.parentSymbol, ident=lookupResult.ident, declaration=declaration, - docname=docname) + docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_indent -= 2 return symbol @@ -1853,7 +1857,7 @@ def makeCandSymbol() -> "Symbol": symbol = Symbol(parent=lookupResult.parentSymbol, ident=lookupResult.ident, declaration=declaration, - docname=docname) + docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_print("end: creating candidate symbol") return symbol @@ -1919,7 +1923,7 @@ def handleDuplicateDeclaration(symbol: "Symbol", candSymbol: "Symbol") -> None: # .. namespace:: Test # .. namespace:: nullptr # .. class:: Test - symbol._fill_empty(declaration, docname) + symbol._fill_empty(declaration, docname, line) return symbol def merge_with(self, other: "Symbol", docnames: List[str], @@ -1940,13 +1944,15 @@ def merge_with(self, other: "Symbol", docnames: List[str], continue if otherChild.declaration and otherChild.docname in docnames: if not ourChild.declaration: - ourChild._fill_empty(otherChild.declaration, otherChild.docname) + ourChild._fill_empty(otherChild.declaration, + otherChild.docname, otherChild.line) elif ourChild.docname != otherChild.docname: name = str(ourChild.declaration) - msg = __("Duplicate C declaration, also defined in '%s'.\n" - "Declaration is '%s'.") - msg = msg % (ourChild.docname, name) - logger.warning(msg, location=otherChild.docname) + msg = __("Duplicate C declaration, also defined at %s:%s.\n" + "Declaration is '.. c:%s:: %s'.") + msg = msg % (ourChild.docname, ourChild.line, + ourChild.declaration.directiveType, name) + logger.warning(msg, location=(otherChild.docname, otherChild.line)) else: # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to @@ -1960,19 +1966,21 @@ def add_name(self, nestedName: ASTNestedName) -> "Symbol": if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("add_name:") - res = self._add_symbols(nestedName, declaration=None, docname=None) + res = self._add_symbols(nestedName, declaration=None, docname=None, line=None) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res - def add_declaration(self, declaration: ASTDeclaration, docname: str) -> "Symbol": + def add_declaration(self, declaration: ASTDeclaration, + docname: str, line: int) -> "Symbol": if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("add_declaration:") - assert declaration - assert docname + assert declaration is not None + assert docname is not None + assert line is not None nestedName = declaration.name - res = self._add_symbols(nestedName, declaration, docname) + res = self._add_symbols(nestedName, declaration, docname, line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res @@ -3149,7 +3157,7 @@ def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None: declClone.enumeratorScopedSymbol = symbol Symbol(parent=targetSymbol, ident=symbol.ident, declaration=declClone, - docname=self.env.docname) + docname=self.env.docname, line=self.get_source_info()[1]) def add_target_and_index(self, ast: ASTDeclaration, sig: str, signode: TextElement) -> None: @@ -3252,7 +3260,8 @@ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration: raise ValueError from e try: - symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) + symbol = parentSymbol.add_declaration( + ast, docname=self.env.docname, line=self.get_source_info()[1]) # append the new declaration to the sibling list assert symbol.siblingAbove is None assert symbol.siblingBelow is None @@ -3265,9 +3274,9 @@ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration: # Assume we are actually in the old symbol, # instead of the newly created duplicate. self.env.temp_data['c:last_symbol'] = e.symbol - msg = __("Duplicate C declaration, also defined in '%s'.\n" - "Declaration is '%s'.") - msg = msg % (e.symbol.docname, sig) + msg = __("Duplicate C declaration, also defined at %s:%s.\n" + "Declaration is '.. c:%s:: %s'.") + msg = msg % (e.symbol.docname, e.symbol.line, self.display_object_type, sig) logger.warning(msg, location=signode) if ast.objectType == 'enumerator': @@ -3694,7 +3703,7 @@ class CDomain(Domain): 'texpr': CExprRole(asCode=False) } initial_data = { - 'root_symbol': Symbol(None, None, None, None), + 'root_symbol': Symbol(None, None, None, None, None), 'objects': {}, # fullname -> docname, node_id, objtype } # type: Dict[str, Union[Symbol, Dict[str, Tuple[str, str, str]]]] diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 0e996532fc3..72d42503550 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -3798,7 +3798,7 @@ def __deepcopy__(self, memo): assert False # shouldn't happen else: # the domain base class makes a copy of the initial data, which is fine - return Symbol(None, None, None, None, None, None) + return Symbol(None, None, None, None, None, None, None) @staticmethod def debug_print(*args: Any) -> None: @@ -3825,7 +3825,8 @@ def __setattr__(self, key: str, value: Any) -> None: def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], templateParams: Union[ASTTemplateParams, ASTTemplateIntroduction], - templateArgs: Any, declaration: ASTDeclaration, docname: str) -> None: + templateArgs: Any, declaration: ASTDeclaration, + docname: str, line: int) -> None: self.parent = parent # declarations in a single directive are linked together self.siblingAbove = None # type: Symbol @@ -3835,6 +3836,7 @@ def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator self.templateArgs = templateArgs # identifier self.declaration = declaration self.docname = docname + self.line = line self.isRedeclaration = False self._assert_invariants() @@ -3850,15 +3852,18 @@ def __init__(self, parent: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator # Do symbol addition after self._children has been initialised. self._add_template_and_function_params() - def _fill_empty(self, declaration: ASTDeclaration, docname: str) -> None: + def _fill_empty(self, declaration: ASTDeclaration, docname: str, line: int) -> None: self._assert_invariants() - assert not self.declaration - assert not self.docname - assert declaration - assert docname + assert self.declaration is None + assert self.docname is None + assert self.line is None + assert declaration is not None + assert docname is not None + assert line is not None self.declaration = declaration self.declaration.symbol = self self.docname = docname + self.line = line self._assert_invariants() # and symbol addition should be done as well self._add_template_and_function_params() @@ -3882,7 +3887,7 @@ def _add_template_and_function_params(self) -> None: decl = None nne = ASTNestedNameElement(tp.get_identifier(), None) nn = ASTNestedName([nne], [False], rooted=False) - self._add_symbols(nn, [], decl, self.docname) + self._add_symbols(nn, [], decl, self.docname, self.line) # add symbols for function parameters, if any if self.declaration is not None and self.declaration.function_params is not None: for fp in self.declaration.function_params: @@ -3895,7 +3900,7 @@ def _add_template_and_function_params(self) -> None: decl = ASTDeclaration('functionParam', None, None, None, None, fp, None) assert not nn.rooted assert len(nn.names) == 1 - self._add_symbols(nn, [], decl, self.docname) + self._add_symbols(nn, [], decl, self.docname, self.line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 @@ -3913,6 +3918,7 @@ def clear_doc(self, docname: str) -> None: if sChild.declaration and sChild.docname == docname: sChild.declaration = None sChild.docname = None + sChild.line = None if sChild.siblingAbove is not None: sChild.siblingAbove.siblingBelow = sChild.siblingBelow if sChild.siblingBelow is not None: @@ -4223,7 +4229,7 @@ def _symbol_lookup(self, nestedName: ASTNestedName, templateDecls: List[Any], identOrOp, templateParams, templateArgs) def _add_symbols(self, nestedName: ASTNestedName, templateDecls: List[Any], - declaration: ASTDeclaration, docname: str) -> "Symbol": + declaration: ASTDeclaration, docname: str, line: int) -> "Symbol": # Used for adding a whole path of symbols, where the last may or may not # be an actual declaration. @@ -4232,9 +4238,9 @@ def _add_symbols(self, nestedName: ASTNestedName, templateDecls: List[Any], Symbol.debug_print("_add_symbols:") Symbol.debug_indent += 1 Symbol.debug_print("tdecls:", ",".join(str(t) for t in templateDecls)) - Symbol.debug_print("nn: ", nestedName) - Symbol.debug_print("decl: ", declaration) - Symbol.debug_print("doc: ", docname) + Symbol.debug_print("nn: ", nestedName) + Symbol.debug_print("decl: ", declaration) + Symbol.debug_print("location: {}:{}".format(docname, line)) def onMissingQualifiedSymbol(parentSymbol: "Symbol", identOrOp: Union[ASTIdentifier, ASTOperator], @@ -4251,7 +4257,7 @@ def onMissingQualifiedSymbol(parentSymbol: "Symbol", return Symbol(parent=parentSymbol, identOrOp=identOrOp, templateParams=templateParams, templateArgs=templateArgs, declaration=None, - docname=None) + docname=None, line=None) lookupResult = self._symbol_lookup(nestedName, templateDecls, onMissingQualifiedSymbol, @@ -4272,14 +4278,14 @@ def onMissingQualifiedSymbol(parentSymbol: "Symbol", Symbol.debug_print("identOrOp: ", lookupResult.identOrOp) Symbol.debug_print("templateArgs: ", lookupResult.templateArgs) Symbol.debug_print("declaration: ", declaration) - Symbol.debug_print("docname: ", docname) + Symbol.debug_print("location: {}:{}".format(docname, line)) Symbol.debug_indent -= 1 symbol = Symbol(parent=lookupResult.parentSymbol, identOrOp=lookupResult.identOrOp, templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, - docname=docname) + docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_indent -= 2 return symbol @@ -4328,7 +4334,7 @@ def makeCandSymbol() -> "Symbol": templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, - docname=docname) + docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_print("end: creating candidate symbol") return symbol @@ -4400,7 +4406,7 @@ def handleDuplicateDeclaration(symbol: "Symbol", candSymbol: "Symbol") -> None: # .. namespace:: Test # .. namespace:: nullptr # .. class:: Test - symbol._fill_empty(declaration, docname) + symbol._fill_empty(declaration, docname, line) return symbol def merge_with(self, other: "Symbol", docnames: List[str], @@ -4479,13 +4485,15 @@ def unconditionalAdd(self, otherChild): continue if otherChild.declaration and otherChild.docname in docnames: if not ourChild.declaration: - ourChild._fill_empty(otherChild.declaration, otherChild.docname) + ourChild._fill_empty(otherChild.declaration, + otherChild.docname, otherChild.line) elif ourChild.docname != otherChild.docname: name = str(ourChild.declaration) - msg = __("Duplicate C++ declaration, also defined in '%s'.\n" - "Declaration is '%s'.") - msg = msg % (ourChild.docname, name) - logger.warning(msg, location=otherChild.docname) + msg = __("Duplicate C++ declaration, also defined at %s:%s.\n" + "Declaration is '.. cpp:%s:: %s'.") + msg = msg % (ourChild.docname, ourChild.line, + ourChild.declaration.directiveType, name) + logger.warning(msg, location=(otherChild.docname, otherChild.line)) else: # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to @@ -4509,23 +4517,25 @@ def add_name(self, nestedName: ASTNestedName, else: templateDecls = [] res = self._add_symbols(nestedName, templateDecls, - declaration=None, docname=None) + declaration=None, docname=None, line=None) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res - def add_declaration(self, declaration: ASTDeclaration, docname: str) -> "Symbol": + def add_declaration(self, declaration: ASTDeclaration, + docname: str, line: int) -> "Symbol": if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("add_declaration:") - assert declaration - assert docname + assert declaration is not None + assert docname is not None + assert line is not None nestedName = declaration.name if declaration.templatePrefix: templateDecls = declaration.templatePrefix.templates else: templateDecls = [] - res = self._add_symbols(nestedName, templateDecls, declaration, docname) + res = self._add_symbols(nestedName, templateDecls, declaration, docname, line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res @@ -4720,7 +4730,8 @@ def onMissingQualifiedSymbol(parentSymbol: "Symbol", templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, - docname='fakeDocnameForQuery') + docname='fakeDocnameForQuery', + line=42) queryId = declaration.get_newest_id() for symbol in symbols: if symbol.declaration is None: @@ -6726,7 +6737,7 @@ def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None: Symbol(parent=targetSymbol, identOrOp=symbol.identOrOp, templateParams=None, templateArgs=None, declaration=declClone, - docname=self.env.docname) + docname=self.env.docname, line=self.get_source_info()[1]) def add_target_and_index(self, ast: ASTDeclaration, sig: str, signode: TextElement) -> None: @@ -6840,7 +6851,7 @@ def run(self) -> List[Node]: return super().run() def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration: - parentSymbol = self.env.temp_data['cpp:parent_symbol'] + parentSymbol = self.env.temp_data['cpp:parent_symbol'] # type: Symbol parser = DefinitionParser(sig, location=signode, config=self.env.config) try: @@ -6856,7 +6867,8 @@ def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration: raise ValueError from e try: - symbol = parentSymbol.add_declaration(ast, docname=self.env.docname) + symbol = parentSymbol.add_declaration( + ast, docname=self.env.docname, line=self.get_source_info()[1]) # append the new declaration to the sibling list assert symbol.siblingAbove is None assert symbol.siblingBelow is None @@ -6869,9 +6881,10 @@ def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration: # Assume we are actually in the old symbol, # instead of the newly created duplicate. self.env.temp_data['cpp:last_symbol'] = e.symbol - msg = __("Duplicate C++ declaration, also defined in '%s'.\n" - "Declaration is '%s'.") - msg = msg % (e.symbol.docname, sig) + msg = __("Duplicate C++ declaration, also defined at %s:%s.\n" + "Declaration is '.. cpp:%s:: %s'.") + msg = msg % (e.symbol.docname, e.symbol.line, + self.display_object_type, sig) logger.warning(msg, location=signode) if ast.objectType == 'enumerator': @@ -7290,7 +7303,7 @@ class CPPDomain(Domain): 'texpr': CPPExprRole(asCode=False) } initial_data = { - 'root_symbol': Symbol(None, None, None, None, None, None), + 'root_symbol': Symbol(None, None, None, None, None, None, None), 'names': {} # full name for indexing -> docname } diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index 43d71f74ede..f1014f6b876 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -54,8 +54,8 @@ def _check(name, input, idDict, output, key, asTextOutput): print("Result: ", res) print("Expected: ", outputAst) raise DefinitionError("") - rootSymbol = Symbol(None, None, None, None) - symbol = rootSymbol.add_declaration(ast, docname="TestDoc") + rootSymbol = Symbol(None, None, None, None, None) + symbol = rootSymbol.add_declaration(ast, docname="TestDoc", line=42) parentNode = addnodes.desc() signode = addnodes.desc_signature(input, '') parentNode += signode diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index ec7a2b262b6..b22a51730ce 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -60,8 +60,8 @@ def _check(name, input, idDict, output, key, asTextOutput): print("Result: ", res) print("Expected: ", outputAst) raise DefinitionError("") - rootSymbol = Symbol(None, None, None, None, None, None) - symbol = rootSymbol.add_declaration(ast, docname="TestDoc") + rootSymbol = Symbol(None, None, None, None, None, None, None) + symbol = rootSymbol.add_declaration(ast, docname="TestDoc", line=42) parentNode = addnodes.desc() signode = addnodes.desc_signature(input, '') parentNode += signode @@ -1246,8 +1246,8 @@ def test_mix_decl_duplicate(app, warning): restructuredtext.parse(app, text) ws = warning.getvalue().split("\n") assert len(ws) == 5 - assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[0] - assert "Declaration is 'void A()'." in ws[1] - assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined in 'index'." in ws[2] - assert "Declaration is 'A'." in ws[3] + assert "index.rst:2: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[0] + assert "Declaration is '.. cpp:function:: void A()'." in ws[1] + assert "index.rst:3: WARNING: Duplicate C++ declaration, also defined at index:1." in ws[2] + assert "Declaration is '.. cpp:struct:: A'." in ws[3] assert ws[4] == "" \ No newline at end of file