from __future__ import annotations from typing import TYPE_CHECKING, Any, Callable, NoReturn from sphinx.domains.cpp._ast import ( ASTDeclaration, ASTIdentifier, ASTNestedName, ASTNestedNameElement, ASTOperator, ASTTemplateArgs, ASTTemplateDeclarationPrefix, ASTTemplateIntroduction, ASTTemplateParams, ) from sphinx.locale import __ from sphinx.util import logging if TYPE_CHECKING: from collections.abc import Iterator from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) class _DuplicateSymbolError(Exception): def __init__(self, symbol: Symbol, declaration: ASTDeclaration) -> None: assert symbol assert declaration self.symbol = symbol self.declaration = declaration def __str__(self) -> str: return "Internal C++ duplicate symbol error:\n%s" % self.symbol.dump(0) class SymbolLookupResult: def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol, identOrOp: ASTIdentifier | ASTOperator, templateParams: Any, templateArgs: ASTTemplateArgs) -> None: self.symbols = symbols self.parentSymbol = parentSymbol self.identOrOp = identOrOp self.templateParams = templateParams self.templateArgs = templateArgs class LookupKey: def __init__(self, data: list[tuple[ASTNestedNameElement, ASTTemplateParams | ASTTemplateIntroduction, str]]) -> None: self.data = data def _is_specialization(templateParams: ASTTemplateParams | ASTTemplateIntroduction, templateArgs: ASTTemplateArgs) -> bool: # Checks if `templateArgs` does not exactly match `templateParams`. # 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 # having no template params and no arguments is also a specialization if len(templateParams.params) == 0: 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 = str(param.name) argTxt = str(arg) isArgPackExpansion = argTxt.endswith('...') if param.isPack != isArgPackExpansion: return True argName = argTxt[:-3] if isArgPackExpansion else argTxt if paramName != argName: return True return False class Symbol: debug_indent = 0 debug_indent_string = " " debug_lookup = False # overridden by the corresponding config value debug_show_tree = False # overridden by the corresponding config value def __copy__(self) -> NoReturn: raise AssertionError # shouldn't happen def __deepcopy__(self, memo: Any) -> Symbol: if self.parent: raise AssertionError # shouldn't happen # the domain base class makes a copy of the initial data, which is fine return Symbol(None, None, None, None, None, None, None) @staticmethod def debug_print(*args: Any) -> None: logger.debug(Symbol.debug_indent_string * Symbol.debug_indent, end="") logger.debug(*args) def _assert_invariants(self) -> None: if not self.parent: # parent == None means global scope, so declaration means a parent assert not self.identOrOp assert not self.templateParams assert not self.templateArgs assert not self.declaration assert not self.docname else: if self.declaration: assert self.docname def __setattr__(self, key: str, value: Any) -> None: if key == "children": raise AssertionError return super().__setattr__(key, value) def __init__(self, parent: Symbol | None, identOrOp: ASTIdentifier | ASTOperator | None, templateParams: ASTTemplateParams | ASTTemplateIntroduction | None, templateArgs: Any, declaration: ASTDeclaration | None, docname: str | None, line: int | None) -> None: self.parent = parent # declarations in a single directive are linked together self.siblingAbove: Symbol | None = None self.siblingBelow: Symbol | None = None self.identOrOp = identOrOp # Ensure the same symbol for `A` is created for: # # .. cpp:class:: template class A # # and # # .. cpp:function:: template int A::foo() if (templateArgs is not None and not _is_specialization(templateParams, templateArgs)): templateArgs = None self.templateParams = templateParams # template self.templateArgs = templateArgs # identifier self.declaration = declaration self.docname = docname self.line = line self.isRedeclaration = False self._assert_invariants() # Remember to modify Symbol.remove if modifications to the parent change. self._children: list[Symbol] = [] self._anonChildren: list[Symbol] = [] # note: _children includes _anonChildren if self.parent: self.parent._children.append(self) if self.declaration: self.declaration.symbol = self # Do symbol addition after self._children has been initialised. self._add_template_and_function_params() def _fill_empty(self, declaration: ASTDeclaration, docname: str, line: int) -> None: self._assert_invariants() 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() def _add_template_and_function_params(self) -> None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("_add_template_and_function_params:") # Note: we may be called from _fill_empty, so the symbols we want # to add may actually already be present (as empty symbols). # add symbols for the template params if self.templateParams: for tp in self.templateParams.params: if not tp.get_identifier(): continue # only add a declaration if we our self are from a declaration if self.declaration: decl = ASTDeclaration(objectType='templateParam', declaration=tp) else: decl = None nne = ASTNestedNameElement(tp.get_identifier(), None) nn = ASTNestedName([nne], [False], rooted=False) 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: if fp.arg is None: continue nn = fp.arg.name if nn is None: continue # (comparing to the template params: we have checked that we are a declaration) decl = ASTDeclaration(objectType='functionParam', declaration=fp) assert not nn.rooted assert len(nn.names) == 1 self._add_symbols(nn, [], decl, self.docname, self.line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 def remove(self) -> None: if self.parent is None: return assert self in self.parent._children self.parent._children.remove(self) self.parent = None def clear_doc(self, docname: str) -> None: newChildren: list[Symbol] = [] for sChild in self._children: sChild.clear_doc(docname) 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: sChild.siblingBelow.siblingAbove = sChild.siblingAbove sChild.siblingAbove = None sChild.siblingBelow = None newChildren.append(sChild) self._children = newChildren def get_all_symbols(self) -> Iterator[Any]: yield self for sChild in self._children: yield from sChild.get_all_symbols() @property def children_recurse_anon(self) -> Iterator[Symbol]: for c in self._children: yield c if not c.identOrOp.is_anon(): continue yield from c.children_recurse_anon 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: symbols.append(s) s = s.parent symbols.reverse() key = [] for s in symbols: nne = ASTNestedNameElement(s.identOrOp, s.templateArgs) 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 s in symbols: names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs)) templates.append(False) return ASTNestedName(names, templates, rooted=False) def _find_first_named_symbol(self, identOrOp: ASTIdentifier | ASTOperator, templateParams: ASTTemplateParams | ASTTemplateIntroduction, templateArgs: ASTTemplateArgs | None, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool, correctPrimaryTemplateArgs: bool, ) -> Symbol | None: if Symbol.debug_lookup: Symbol.debug_print("_find_first_named_symbol ->") res = self._find_named_symbols(identOrOp, templateParams, templateArgs, templateShorthand, matchSelf, recurseInAnon, correctPrimaryTemplateArgs, searchInSiblings=False) try: return next(res) except StopIteration: return None def _find_named_symbols(self, identOrOp: ASTIdentifier | ASTOperator, templateParams: ASTTemplateParams | ASTTemplateIntroduction, templateArgs: ASTTemplateArgs, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool, correctPrimaryTemplateArgs: bool, searchInSiblings: bool) -> Iterator[Symbol]: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("_find_named_symbols:") Symbol.debug_indent += 1 Symbol.debug_print("self:") logger.debug(self.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_print("identOrOp: ", identOrOp) Symbol.debug_print("templateParams: ", templateParams) Symbol.debug_print("templateArgs: ", templateArgs) Symbol.debug_print("templateShorthand: ", templateShorthand) Symbol.debug_print("matchSelf: ", matchSelf) Symbol.debug_print("recurseInAnon: ", recurseInAnon) Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs) Symbol.debug_print("searchInSiblings: ", searchInSiblings) if correctPrimaryTemplateArgs: 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 int A::var; if not _is_specialization(templateParams, templateArgs): templateArgs = None def matches(s: Symbol) -> bool: if s.identOrOp != identOrOp: return False if (s.templateParams is None) != (templateParams is None): if templateParams is not None: # we query with params, they must match params return False if not templateShorthand: # we don't query with params, and we do care about them return False if templateParams: # TODO: do better comparison if str(s.templateParams) != str(templateParams): return False if (s.templateArgs is None) != (templateArgs is None): return False if s.templateArgs: # TODO: do better comparison if str(s.templateArgs) != str(templateArgs): return False return True def candidates() -> Iterator[Symbol]: s = self if Symbol.debug_lookup: Symbol.debug_print("searching in self:") logger.debug(s.to_string(Symbol.debug_indent + 1), end="") while True: if matchSelf: yield s if recurseInAnon: yield from s.children_recurse_anon else: yield from s._children if s.siblingAbove is None: break s = s.siblingAbove if Symbol.debug_lookup: Symbol.debug_print("searching in sibling:") logger.debug(s.to_string(Symbol.debug_indent + 1), end="") for s in candidates(): if Symbol.debug_lookup: Symbol.debug_print("candidate:") logger.debug(s.to_string(Symbol.debug_indent + 1), end="") if matches(s): if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("matches") Symbol.debug_indent -= 3 yield s if Symbol.debug_lookup: Symbol.debug_indent += 2 if Symbol.debug_lookup: Symbol.debug_indent -= 2 def _symbol_lookup( self, nestedName: ASTNestedName, templateDecls: list[Any], onMissingQualifiedSymbol: Callable[ [Symbol, ASTIdentifier | ASTOperator, Any, ASTTemplateArgs], Symbol | None, ], strictTemplateParamArgLists: bool, ancestorLookupType: str, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool, correctPrimaryTemplateArgs: bool, searchInSiblings: bool, ) -> SymbolLookupResult: # ancestorLookupType: if not None, specifies the target type of the lookup if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("_symbol_lookup:") Symbol.debug_indent += 1 Symbol.debug_print("self:") logger.debug(self.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_print("nestedName: ", nestedName) Symbol.debug_print("templateDecls: ", ",".join(str(t) for t in templateDecls)) Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists) Symbol.debug_print("ancestorLookupType:", ancestorLookupType) Symbol.debug_print("templateShorthand: ", templateShorthand) Symbol.debug_print("matchSelf: ", matchSelf) Symbol.debug_print("recurseInAnon: ", recurseInAnon) Symbol.debug_print("correctPrimaryTemplateArgs: ", correctPrimaryTemplateArgs) Symbol.debug_print("searchInSiblings: ", searchInSiblings) if strictTemplateParamArgLists: # Each template argument list must have a template parameter list. # But to declare a template there must be an additional template parameter list. assert (nestedName.num_templates() == len(templateDecls) or nestedName.num_templates() + 1 == len(templateDecls)) else: assert len(templateDecls) <= nestedName.num_templates() + 1 names = nestedName.names # find the right starting point for lookup parentSymbol = self if nestedName.rooted: while parentSymbol.parent: parentSymbol = parentSymbol.parent if ancestorLookupType is not None: # walk up until we find the first identifier firstName = names[0] if not firstName.is_operator(): while parentSymbol.parent: if parentSymbol.find_identifier(firstName.identOrOp, matchSelf=matchSelf, recurseInAnon=recurseInAnon, searchInSiblings=searchInSiblings): # if we are in the scope of a constructor but wants to # reference the class we need to walk one extra up if (len(names) == 1 and ancestorLookupType == 'class' and matchSelf and parentSymbol.parent and parentSymbol.parent.identOrOp == firstName.identOrOp): pass else: break parentSymbol = parentSymbol.parent if Symbol.debug_lookup: Symbol.debug_print("starting point:") logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="") # and now the actual lookup iTemplateDecl = 0 for name in names[:-1]: identOrOp = name.identOrOp templateArgs = name.templateArgs if strictTemplateParamArgLists: # there must be a parameter list if templateArgs: assert iTemplateDecl < len(templateDecls) templateParams = templateDecls[iTemplateDecl] iTemplateDecl += 1 else: templateParams = None else: # take the next template parameter list if there is one # otherwise it's ok if templateArgs and iTemplateDecl < len(templateDecls): templateParams = templateDecls[iTemplateDecl] iTemplateDecl += 1 else: templateParams = None symbol = parentSymbol._find_first_named_symbol( identOrOp, templateParams, templateArgs, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=correctPrimaryTemplateArgs) if symbol is None: symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp, templateParams, templateArgs) if symbol is None: if Symbol.debug_lookup: Symbol.debug_indent -= 2 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) matchSelf = False parentSymbol = symbol if Symbol.debug_lookup: Symbol.debug_print("handle last name from:") logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="") # handle the last name name = names[-1] identOrOp = name.identOrOp templateArgs = name.templateArgs if iTemplateDecl < len(templateDecls): assert iTemplateDecl + 1 == len(templateDecls) templateParams = templateDecls[iTemplateDecl] else: assert iTemplateDecl == len(templateDecls) templateParams = None symbols = parentSymbol._find_named_symbols( identOrOp, templateParams, templateArgs, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False, searchInSiblings=searchInSiblings) if Symbol.debug_lookup: symbols = list(symbols) # type: ignore[assignment] Symbol.debug_indent -= 2 return SymbolLookupResult(symbols, parentSymbol, identOrOp, templateParams, templateArgs) def _add_symbols( self, nestedName: ASTNestedName, templateDecls: list[Any], declaration: ASTDeclaration | None, docname: str | None, line: int | None, ) -> Symbol: # Used for adding a whole path of symbols, where the last may or may not # be an actual declaration. if Symbol.debug_lookup: Symbol.debug_indent += 1 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(f"location: {docname}:{line}") def onMissingQualifiedSymbol(parentSymbol: Symbol, identOrOp: ASTIdentifier | ASTOperator, templateParams: Any, templateArgs: ASTTemplateArgs, ) -> Symbol | None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:") Symbol.debug_indent += 1 Symbol.debug_print("templateParams:", templateParams) Symbol.debug_print("identOrOp: ", identOrOp) Symbol.debug_print("templateARgs: ", templateArgs) Symbol.debug_indent -= 2 return Symbol(parent=parentSymbol, identOrOp=identOrOp, templateParams=templateParams, templateArgs=templateArgs, declaration=None, docname=None, line=None) lookupResult = self._symbol_lookup(nestedName, templateDecls, onMissingQualifiedSymbol, strictTemplateParamArgLists=True, ancestorLookupType=None, templateShorthand=False, matchSelf=False, recurseInAnon=False, correctPrimaryTemplateArgs=True, searchInSiblings=False) assert lookupResult is not None # we create symbols all the way, so that can't happen symbols = list(lookupResult.symbols) if len(symbols) == 0: if Symbol.debug_lookup: Symbol.debug_print("_add_symbols, result, no symbol:") Symbol.debug_indent += 1 Symbol.debug_print("templateParams:", lookupResult.templateParams) Symbol.debug_print("identOrOp: ", lookupResult.identOrOp) Symbol.debug_print("templateArgs: ", lookupResult.templateArgs) Symbol.debug_print("declaration: ", declaration) Symbol.debug_print(f"location: {docname}:{line}") Symbol.debug_indent -= 1 symbol = Symbol(parent=lookupResult.parentSymbol, identOrOp=lookupResult.identOrOp, templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_indent -= 2 return symbol if Symbol.debug_lookup: Symbol.debug_print("_add_symbols, result, symbols:") Symbol.debug_indent += 1 Symbol.debug_print("number symbols:", len(symbols)) Symbol.debug_indent -= 1 if not declaration: if Symbol.debug_lookup: Symbol.debug_print("no declaration") Symbol.debug_indent -= 2 # good, just a scope creation # TODO: what if we have more than one symbol? return symbols[0] noDecl = [] withDecl = [] dupDecl = [] for s in symbols: if s.declaration is None: noDecl.append(s) elif s.isRedeclaration: dupDecl.append(s) else: withDecl.append(s) if Symbol.debug_lookup: Symbol.debug_print("#noDecl: ", len(noDecl)) Symbol.debug_print("#withDecl:", len(withDecl)) Symbol.debug_print("#dupDecl: ", len(dupDecl)) # With partial builds we may start with a large symbol tree stripped of declarations. # Essentially any combination of noDecl, withDecl, and dupDecls seems possible. # TODO: make partial builds fully work. What should happen when the primary symbol gets # deleted, and other duplicates exist? The full document should probably be rebuild. # First check if one of those with a declaration matches. # If it's a function, we need to compare IDs, # otherwise there should be only one symbol with a declaration. def makeCandSymbol() -> Symbol: if Symbol.debug_lookup: Symbol.debug_print("begin: creating candidate symbol") symbol = Symbol(parent=lookupResult.parentSymbol, identOrOp=lookupResult.identOrOp, templateParams=lookupResult.templateParams, templateArgs=lookupResult.templateArgs, declaration=declaration, docname=docname, line=line) if Symbol.debug_lookup: Symbol.debug_print("end: creating candidate symbol") return symbol if len(withDecl) == 0: candSymbol = None else: candSymbol = makeCandSymbol() def handleDuplicateDeclaration(symbol: Symbol, candSymbol: Symbol) -> None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("redeclaration") Symbol.debug_indent -= 1 Symbol.debug_indent -= 2 # 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. candSymbol.isRedeclaration = True raise _DuplicateSymbolError(symbol, declaration) if declaration.objectType != "function": assert len(withDecl) <= 1 handleDuplicateDeclaration(withDecl[0], candSymbol) # (not reachable) # a function, so compare IDs candId = declaration.get_newest_id() if Symbol.debug_lookup: Symbol.debug_print("candId:", candId) for symbol in withDecl: # but all existing must be functions as well, # otherwise we declare it to be a duplicate if symbol.declaration.objectType != 'function': handleDuplicateDeclaration(symbol, candSymbol) # (not reachable) oldId = symbol.declaration.get_newest_id() if Symbol.debug_lookup: Symbol.debug_print("oldId: ", oldId) if candId == oldId: handleDuplicateDeclaration(symbol, candSymbol) # (not reachable) # no candidate symbol found with matching ID # if there is an empty symbol, fill that one if len(noDecl) == 0: if Symbol.debug_lookup: Symbol.debug_print("no match, no empty") if candSymbol is not None: Symbol.debug_print("result is already created candSymbol") else: Symbol.debug_print("result is makeCandSymbol()") Symbol.debug_indent -= 2 if candSymbol is not None: return candSymbol else: return makeCandSymbol() else: if Symbol.debug_lookup: Symbol.debug_print( "no match, but fill an empty declaration, candSybmol is not None?:", candSymbol is not None, ) Symbol.debug_indent -= 2 if candSymbol is not None: candSymbol.remove() # assert len(noDecl) == 1 # TODO: enable assertion when we at some point find out how to do cleanup # for now, just take the first one, it should work fine ... right? symbol = noDecl[0] # If someone first opened the scope, and then later # declares it, e.g, # .. namespace:: Test # .. namespace:: nullptr # .. class:: Test symbol._fill_empty(declaration, docname, line) return symbol def merge_with(self, other: Symbol, docnames: list[str], env: BuildEnvironment) -> None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("merge_with:") assert other is not None def unconditionalAdd(self: Symbol, otherChild: Symbol) -> None: # TODO: hmm, should we prune by docnames? self._children.append(otherChild) otherChild.parent = self otherChild._assert_invariants() if Symbol.debug_lookup: Symbol.debug_indent += 1 for otherChild in other._children: if Symbol.debug_lookup: Symbol.debug_print("otherChild:\n", otherChild.to_string(Symbol.debug_indent)) Symbol.debug_indent += 1 if otherChild.isRedeclaration: unconditionalAdd(self, otherChild) if Symbol.debug_lookup: Symbol.debug_print("isRedeclaration") Symbol.debug_indent -= 1 continue candiateIter = self._find_named_symbols( identOrOp=otherChild.identOrOp, templateParams=otherChild.templateParams, templateArgs=otherChild.templateArgs, templateShorthand=False, matchSelf=False, recurseInAnon=False, correctPrimaryTemplateArgs=False, searchInSiblings=False) candidates = list(candiateIter) if Symbol.debug_lookup: Symbol.debug_print("raw candidate symbols:", len(candidates)) symbols = [s for s in candidates if not s.isRedeclaration] if Symbol.debug_lookup: Symbol.debug_print("non-duplicate candidate symbols:", len(symbols)) if len(symbols) == 0: unconditionalAdd(self, otherChild) if Symbol.debug_lookup: Symbol.debug_indent -= 1 continue ourChild = None if otherChild.declaration is None: if Symbol.debug_lookup: Symbol.debug_print("no declaration in other child") ourChild = symbols[0] else: queryId = otherChild.declaration.get_newest_id() if Symbol.debug_lookup: Symbol.debug_print("queryId: ", queryId) for symbol in symbols: if symbol.declaration is None: if Symbol.debug_lookup: Symbol.debug_print("empty candidate") # if in the end we have non-matching, but have an empty one, # then just continue with that ourChild = symbol continue candId = symbol.declaration.get_newest_id() if Symbol.debug_lookup: Symbol.debug_print("candidate:", candId) if candId == queryId: ourChild = symbol break if Symbol.debug_lookup: Symbol.debug_indent -= 1 if ourChild is None: unconditionalAdd(self, otherChild) continue if otherChild.declaration and otherChild.docname in docnames: if not ourChild.declaration: ourChild._fill_empty(otherChild.declaration, otherChild.docname, otherChild.line) elif ourChild.docname != otherChild.docname: name = str(ourChild.declaration) 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: if (otherChild.declaration.objectType == ourChild.declaration.objectType and otherChild.declaration.objectType in ('templateParam', 'functionParam') and ourChild.parent.declaration == otherChild.parent.declaration): # `ourChild` was just created during merging by the call # to `_fill_empty` on the parent and can be ignored. pass else: # Both have declarations, and in the same docname. # This can apparently happen, it should be safe to # just ignore it, right? # Hmm, only on duplicate declarations, right? msg = "Internal C++ domain error during symbol merging.\n" msg += "ourChild:\n" + ourChild.to_string(1) msg += "\notherChild:\n" + otherChild.to_string(1) logger.warning(msg, location=otherChild.docname) ourChild.merge_with(otherChild, docnames, env) if Symbol.debug_lookup: Symbol.debug_indent -= 2 def add_name(self, nestedName: ASTNestedName, templatePrefix: ASTTemplateDeclarationPrefix | None = None) -> Symbol: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("add_name:") if templatePrefix: templateDecls = templatePrefix.templates else: templateDecls = [] res = self._add_symbols(nestedName, templateDecls, declaration=None, docname=None, line=None) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res 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 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, line) if Symbol.debug_lookup: Symbol.debug_indent -= 1 return res def find_identifier(self, identOrOp: ASTIdentifier | ASTOperator, matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool, ) -> Symbol | None: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("find_identifier:") Symbol.debug_indent += 1 Symbol.debug_print("identOrOp: ", identOrOp) Symbol.debug_print("matchSelf: ", matchSelf) Symbol.debug_print("recurseInAnon: ", recurseInAnon) Symbol.debug_print("searchInSiblings:", searchInSiblings) logger.debug(self.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_indent -= 2 current = self while current is not None: if Symbol.debug_lookup: Symbol.debug_indent += 2 Symbol.debug_print("trying:") logger.debug(current.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_indent -= 2 if matchSelf and current.identOrOp == identOrOp: return current children = current.children_recurse_anon if recurseInAnon else current._children for s in children: if s.identOrOp == identOrOp: return s if not searchInSiblings: break current = current.siblingAbove return None def direct_lookup(self, key: LookupKey) -> Symbol: if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("direct_lookup:") Symbol.debug_indent += 1 s = self 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 Symbol.debug_lookup: Symbol.debug_print("name: ", name) Symbol.debug_print("templateParams:", templateParams) Symbol.debug_print("id: ", id_) if s is not None: logger.debug(s.to_string(Symbol.debug_indent + 1), end="") else: Symbol.debug_print("not found") if s is None: if Symbol.debug_lookup: Symbol.debug_indent -= 2 return None if Symbol.debug_lookup: Symbol.debug_indent -= 2 return s def find_name( self, nestedName: ASTNestedName, templateDecls: list[Any], typ: str, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool, ) -> tuple[list[Symbol] | None, str]: # templateShorthand: missing template parameter lists for templates is ok # If the first component is None, # then the second component _may_ be a string explaining why. if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("find_name:") Symbol.debug_indent += 1 Symbol.debug_print("self:") logger.debug(self.to_string(Symbol.debug_indent + 1), end="") Symbol.debug_print("nestedName: ", nestedName) Symbol.debug_print("templateDecls: ", templateDecls) Symbol.debug_print("typ: ", typ) Symbol.debug_print("templateShorthand:", templateShorthand) Symbol.debug_print("matchSelf: ", matchSelf) Symbol.debug_print("recurseInAnon: ", recurseInAnon) Symbol.debug_print("searchInSiblings: ", searchInSiblings) class QualifiedSymbolIsTemplateParam(Exception): pass def onMissingQualifiedSymbol(parentSymbol: Symbol, identOrOp: ASTIdentifier | ASTOperator, templateParams: Any, templateArgs: ASTTemplateArgs) -> Symbol | None: # TODO: Maybe search without template args? # Though, the correctPrimaryTemplateArgs does # that for primary templates. # Is there another case where it would be good? if parentSymbol.declaration is not None: if parentSymbol.declaration.objectType == 'templateParam': raise QualifiedSymbolIsTemplateParam return None try: lookupResult = self._symbol_lookup(nestedName, templateDecls, onMissingQualifiedSymbol, strictTemplateParamArgLists=False, ancestorLookupType=typ, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False, searchInSiblings=searchInSiblings) except QualifiedSymbolIsTemplateParam: return None, "templateParamInQualified" if lookupResult is None: # if it was a part of the qualification that could not be found if Symbol.debug_lookup: Symbol.debug_indent -= 2 return None, None res = list(lookupResult.symbols) if len(res) != 0: if Symbol.debug_lookup: Symbol.debug_indent -= 2 return res, None if lookupResult.parentSymbol.declaration is not None: if lookupResult.parentSymbol.declaration.objectType == 'templateParam': return None, "templateParamInQualified" # try without template params and args symbol = lookupResult.parentSymbol._find_first_named_symbol( lookupResult.identOrOp, None, None, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False) if Symbol.debug_lookup: Symbol.debug_indent -= 2 if symbol is not None: return [symbol], None else: return None, None def find_declaration(self, declaration: ASTDeclaration, typ: str, templateShorthand: bool, matchSelf: bool, recurseInAnon: bool) -> Symbol | None: # templateShorthand: missing template parameter lists for templates is ok if Symbol.debug_lookup: Symbol.debug_indent += 1 Symbol.debug_print("find_declaration:") nestedName = declaration.name if declaration.templatePrefix: templateDecls = declaration.templatePrefix.templates else: templateDecls = [] def onMissingQualifiedSymbol(parentSymbol: Symbol, identOrOp: ASTIdentifier | ASTOperator, templateParams: Any, templateArgs: ASTTemplateArgs) -> Symbol | None: return None lookupResult = self._symbol_lookup(nestedName, templateDecls, onMissingQualifiedSymbol, strictTemplateParamArgLists=False, ancestorLookupType=typ, templateShorthand=templateShorthand, matchSelf=matchSelf, recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False, searchInSiblings=False) if Symbol.debug_lookup: Symbol.debug_indent -= 1 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', line=42) queryId = declaration.get_newest_id() for symbol in symbols: if symbol.declaration is None: continue candId = symbol.declaration.get_newest_id() if candId == queryId: querySymbol.remove() return symbol querySymbol.remove() return None def to_string(self, indent: int) -> str: res = [Symbol.debug_indent_string * indent] if not self.parent: res.append('::') else: if self.templateParams: res.append(str(self.templateParams)) res.append('\n') res.append(Symbol.debug_indent_string * indent) if self.identOrOp: res.append(str(self.identOrOp)) else: res.append(str(self.declaration)) if self.templateArgs: res.append(str(self.templateArgs)) if self.declaration: res.append(": ") if self.isRedeclaration: res.append('!!duplicate!! ') res.append("{" + self.declaration.objectType + "} ") res.append(str(self.declaration)) if self.docname: res.append('\t(') res.append(self.docname) res.append(')') res.append('\n') return ''.join(res) def dump(self, indent: int) -> str: return ''.join([ self.to_string(indent), *(c.dump(indent + 1) for c in self._children), ])