sphinx/sphinx/domains/cpp/_symbol.py

1239 lines
48 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING, Any, 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 Callable, Iterable, 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(
template_params: ASTTemplateParams | ASTTemplateIntroduction,
template_args: ASTTemplateArgs,
) -> bool:
# Checks if `template_args` does not exactly match `template_params`.
# 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(template_params.params) != len(template_args.args):
return True
# having no template params and no arguments is also a specialization
if len(template_params.params) == 0:
return True
for i in range(len(template_params.params)):
param = template_params.params[i]
arg = template_args.args[i]
# TODO: doing this by string manipulation is probably not the most efficient
param_name = str(param.name)
arg_txt = str(arg)
is_arg_pack_expansion = arg_txt.endswith('...')
if param.isPack != is_arg_pack_expansion:
return True
arg_name = arg_txt[:-3] if is_arg_pack_expansion else arg_txt
if param_name != arg_name:
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 <typename T> class A
#
# and
#
# .. cpp:function:: template <typename T> int A<T>::foo()
if templateArgs is not None and not _is_specialization(
templateParams, templateArgs
):
templateArgs = None
self.templateParams = templateParams # template<templateParams>
self.templateArgs = templateArgs # identifier<templateArgs>
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 __repr__(self) -> str:
return f'<Symbol {self.to_string(indent=0)!r}>'
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:
new_children: list[Symbol] = []
for s_child in self._children:
s_child.clear_doc(docname)
if s_child.declaration and s_child.docname == docname:
s_child.declaration = None
s_child.docname = None
s_child.line = None
if s_child.siblingAbove is not None:
s_child.siblingAbove.siblingBelow = s_child.siblingBelow
if s_child.siblingBelow is not None:
s_child.siblingBelow.siblingAbove = s_child.siblingAbove
s_child.siblingAbove = None
s_child.siblingBelow = None
new_children.append(s_child)
self._children = new_children
def get_all_symbols(self) -> Iterator[Any]:
yield self
for s_child in self._children:
yield from s_child.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,
ident_or_op: ASTIdentifier | ASTOperator,
template_params: ASTTemplateParams | ASTTemplateIntroduction,
template_args: ASTTemplateArgs | None,
template_shorthand: bool,
match_self: bool,
recurse_in_anon: bool,
correct_primary_template_args: bool,
) -> Symbol | None:
if Symbol.debug_lookup:
Symbol.debug_print('_find_first_named_symbol ->')
res = self._find_named_symbols(
ident_or_op,
template_params,
template_args,
template_shorthand,
match_self,
recurse_in_anon,
correct_primary_template_args,
search_in_siblings=False,
)
try:
return next(res)
except StopIteration:
return None
def _find_named_symbols(
self,
ident_or_op: ASTIdentifier | ASTOperator,
template_params: ASTTemplateParams | ASTTemplateIntroduction,
template_args: ASTTemplateArgs,
template_shorthand: bool,
match_self: bool,
recurse_in_anon: bool,
correct_primary_template_args: bool,
search_in_siblings: 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('ident_or_op: ', ident_or_op)
Symbol.debug_print('template_params: ', template_params)
Symbol.debug_print('template_args: ', template_args)
Symbol.debug_print('template_shorthand: ', template_shorthand)
Symbol.debug_print('match_self: ', match_self)
Symbol.debug_print('recurse_in_anon: ', recurse_in_anon)
Symbol.debug_print(
'correct_primary_template_args:', correct_primary_template_args
)
Symbol.debug_print('search_in_siblings: ', search_in_siblings)
if correct_primary_template_args:
if template_params is not None and template_args 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 _is_specialization(template_params, template_args):
template_args = None
def matches(s: Symbol) -> bool:
if s.identOrOp != ident_or_op:
return False
if (s.templateParams is None) != (template_params is None):
if template_params is not None:
# we query with params, they must match params
return False
if not template_shorthand:
# we don't query with params, and we do care about them
return False
if template_params:
# TODO: do better comparison
if str(s.templateParams) != str(template_params):
return False
if (s.templateArgs is None) != (template_args is None):
return False
if s.templateArgs:
# TODO: do better comparison
if str(s.templateArgs) != str(template_args):
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 match_self:
yield s
if recurse_in_anon:
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,
nested_name: ASTNestedName,
template_decls: list[Any],
on_missing_qualified_symbol: Callable[
[Symbol, ASTIdentifier | ASTOperator, Any, ASTTemplateArgs],
Symbol | None,
],
strict_template_param_arg_lists: bool,
ancestor_lookup_type: str,
template_shorthand: bool,
match_self: bool,
recurse_in_anon: bool,
correct_primary_template_args: bool,
search_in_siblings: bool,
) -> SymbolLookupResult:
# ancestor_lookup_type: 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('nested_name: ', nested_name)
Symbol.debug_print(
'template_decls: ', ','.join(str(t) for t in template_decls)
)
Symbol.debug_print(
'strict_template_param_arg_lists:', strict_template_param_arg_lists
)
Symbol.debug_print('ancestor_lookup_type:', ancestor_lookup_type)
Symbol.debug_print('template_shorthand: ', template_shorthand)
Symbol.debug_print('match_self: ', match_self)
Symbol.debug_print('recurse_in_anon: ', recurse_in_anon)
Symbol.debug_print(
'correct_primary_template_args: ', correct_primary_template_args
)
Symbol.debug_print('search_in_siblings: ', search_in_siblings)
if strict_template_param_arg_lists:
# Each template argument list must have a template parameter list.
# But to declare a template there must be an additional template parameter list.
num_nested_templates = nested_name.num_templates()
num_template_decls = len(template_decls)
assert (
num_nested_templates == num_template_decls
or num_nested_templates + 1 == num_template_decls
)
else:
assert len(template_decls) <= nested_name.num_templates() + 1
names = nested_name.names
# find the right starting point for lookup
parent_symbol = self
if nested_name.rooted:
while parent_symbol.parent:
parent_symbol = parent_symbol.parent
if ancestor_lookup_type is not None:
# walk up until we find the first identifier
first_name = names[0]
if not first_name.is_operator():
while parent_symbol.parent:
if parent_symbol.find_identifier(
first_name.identOrOp,
matchSelf=match_self,
recurseInAnon=recurse_in_anon,
searchInSiblings=search_in_siblings,
):
# 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 ancestor_lookup_type == 'class'
and match_self
and parent_symbol.parent
and parent_symbol.parent.identOrOp == first_name.identOrOp
):
pass
else:
break
parent_symbol = parent_symbol.parent
if Symbol.debug_lookup:
Symbol.debug_print('starting point:')
logger.debug(parent_symbol.to_string(Symbol.debug_indent + 1), end='')
# and now the actual lookup
i_template_decl = 0
for name in names[:-1]:
ident_or_op = name.identOrOp
template_args = name.templateArgs
if strict_template_param_arg_lists:
# there must be a parameter list
if template_args:
assert i_template_decl < len(template_decls)
template_params = template_decls[i_template_decl]
i_template_decl += 1
else:
template_params = None
else:
# take the next template parameter list if there is one
# otherwise it's ok
if template_args and i_template_decl < len(template_decls):
template_params = template_decls[i_template_decl]
i_template_decl += 1
else:
template_params = None
symbol = parent_symbol._find_first_named_symbol(
ident_or_op,
template_params,
template_args,
template_shorthand=template_shorthand,
match_self=match_self,
recurse_in_anon=recurse_in_anon,
correct_primary_template_args=correct_primary_template_args,
)
if symbol is None:
symbol = on_missing_qualified_symbol(
parent_symbol, ident_or_op, template_params, template_args
)
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 match_self before, we definitely shouldn't
# even more. (see also issue #2666)
match_self = False
parent_symbol = symbol
if Symbol.debug_lookup:
Symbol.debug_print('handle last name from:')
logger.debug(parent_symbol.to_string(Symbol.debug_indent + 1), end='')
# handle the last name
name = names[-1]
ident_or_op = name.identOrOp
template_args = name.templateArgs
if i_template_decl < len(template_decls):
assert i_template_decl + 1 == len(template_decls)
template_params = template_decls[i_template_decl]
else:
assert i_template_decl == len(template_decls)
template_params = None
symbols = parent_symbol._find_named_symbols(
ident_or_op,
template_params,
template_args,
template_shorthand=template_shorthand,
match_self=match_self,
recurse_in_anon=recurse_in_anon,
correct_primary_template_args=False,
search_in_siblings=search_in_siblings,
)
if Symbol.debug_lookup:
symbols = list(symbols) # type: ignore[assignment]
Symbol.debug_indent -= 2
return SymbolLookupResult(
symbols, parent_symbol, ident_or_op, template_params, template_args
)
def _add_symbols(
self,
nested_name: ASTNestedName,
template_decls: 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 template_decls))
Symbol.debug_print('nn: ', nested_name)
Symbol.debug_print('decl: ', declaration)
Symbol.debug_print(f'location: {docname}:{line}')
def on_missing_qualified_symbol(
parent_symbol: Symbol,
ident_or_op: ASTIdentifier | ASTOperator,
template_params: Any,
template_args: ASTTemplateArgs,
) -> Symbol | None:
if Symbol.debug_lookup:
Symbol.debug_indent += 1
Symbol.debug_print('_add_symbols, on_missing_qualified_symbol:')
Symbol.debug_indent += 1
Symbol.debug_print('template_params:', template_params)
Symbol.debug_print('ident_or_op: ', ident_or_op)
Symbol.debug_print('template_args: ', template_args)
Symbol.debug_indent -= 2
return Symbol(
parent=parent_symbol,
identOrOp=ident_or_op,
templateParams=template_params,
templateArgs=template_args,
declaration=None,
docname=None,
line=None,
)
lookup_result = self._symbol_lookup(
nested_name,
template_decls,
on_missing_qualified_symbol,
strict_template_param_arg_lists=True,
ancestor_lookup_type=None,
template_shorthand=False,
match_self=False,
recurse_in_anon=False,
correct_primary_template_args=True,
search_in_siblings=False,
)
# we create symbols all the way, so that can't happen
assert lookup_result is not None
symbols = list(lookup_result.symbols)
if len(symbols) == 0:
if Symbol.debug_lookup:
Symbol.debug_print('_add_symbols, result, no symbol:')
Symbol.debug_indent += 1
Symbol.debug_print('template_params:', lookup_result.templateParams)
Symbol.debug_print('ident_or_op: ', lookup_result.identOrOp)
Symbol.debug_print('template_args: ', lookup_result.templateArgs)
Symbol.debug_print('declaration: ', declaration)
Symbol.debug_print(f'location: {docname}:{line}')
Symbol.debug_indent -= 1
symbol = Symbol(
parent=lookup_result.parentSymbol,
identOrOp=lookup_result.identOrOp,
templateParams=lookup_result.templateParams,
templateArgs=lookup_result.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]
no_decl = []
with_decl = []
dup_decl = []
for s in symbols:
if s.declaration is None:
no_decl.append(s)
elif s.isRedeclaration:
dup_decl.append(s)
else:
with_decl.append(s)
if Symbol.debug_lookup:
Symbol.debug_print('#no_decl: ', len(no_decl))
Symbol.debug_print('#with_decl:', len(with_decl))
Symbol.debug_print('#dup_decl: ', len(dup_decl))
# With partial builds we may start with a large symbol tree stripped of declarations.
# Essentially any combination of no_decl, with_decl, and dup_decls 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 make_cand_symbol() -> Symbol:
if Symbol.debug_lookup:
Symbol.debug_print('begin: creating candidate symbol')
symbol = Symbol(
parent=lookup_result.parentSymbol,
identOrOp=lookup_result.identOrOp,
templateParams=lookup_result.templateParams,
templateArgs=lookup_result.templateArgs,
declaration=declaration,
docname=docname,
line=line,
)
if Symbol.debug_lookup:
Symbol.debug_print('end: creating candidate symbol')
return symbol
if len(with_decl) == 0:
cand_symbol = None
else:
cand_symbol = make_cand_symbol()
def handle_duplicate_declaration(
symbol: Symbol, cand_symbol: 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.
cand_symbol.isRedeclaration = True
raise _DuplicateSymbolError(symbol, declaration)
if declaration.objectType != 'function':
assert len(with_decl) <= 1
handle_duplicate_declaration(with_decl[0], cand_symbol)
# (not reachable)
# a function, so compare IDs
cand_id = declaration.get_newest_id()
if Symbol.debug_lookup:
Symbol.debug_print('cand_id:', cand_id)
for symbol in with_decl:
# but all existing must be functions as well,
# otherwise we declare it to be a duplicate
if symbol.declaration.objectType != 'function':
handle_duplicate_declaration(symbol, cand_symbol)
# (not reachable)
old_id = symbol.declaration.get_newest_id()
if Symbol.debug_lookup:
Symbol.debug_print('old_id: ', old_id)
if cand_id == old_id:
handle_duplicate_declaration(symbol, cand_symbol)
# (not reachable)
# no candidate symbol found with matching ID
# if there is an empty symbol, fill that one
if len(no_decl) == 0:
if Symbol.debug_lookup:
Symbol.debug_print('no match, no empty')
if cand_symbol is not None:
Symbol.debug_print('result is already created cand_symbol')
else:
Symbol.debug_print('result is make_cand_symbol()')
Symbol.debug_indent -= 2
if cand_symbol is not None:
return cand_symbol
else:
return make_cand_symbol()
else:
if Symbol.debug_lookup:
Symbol.debug_print(
'no match, but fill an empty declaration, cand_sybmol is not None?:',
cand_symbol is not None,
)
Symbol.debug_indent -= 2
if cand_symbol is not None:
cand_symbol.remove()
# assert len(no_decl) == 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 = no_decl[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 unconditional_add(self: Symbol, other_child: Symbol) -> None:
# TODO: hmm, should we prune by docnames?
self._children.append(other_child)
other_child.parent = self
other_child._assert_invariants()
if Symbol.debug_lookup:
Symbol.debug_indent += 1
for other_child in other._children:
if Symbol.debug_lookup:
Symbol.debug_print(
'other_child:\n', other_child.to_string(Symbol.debug_indent)
)
Symbol.debug_indent += 1
if other_child.isRedeclaration:
unconditional_add(self, other_child)
if Symbol.debug_lookup:
Symbol.debug_print('is_redeclaration')
Symbol.debug_indent -= 1
continue
candiate_iter = self._find_named_symbols(
ident_or_op=other_child.identOrOp,
template_params=other_child.templateParams,
template_args=other_child.templateArgs,
template_shorthand=False,
match_self=False,
recurse_in_anon=False,
correct_primary_template_args=False,
search_in_siblings=False,
)
candidates = list(candiate_iter)
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:
unconditional_add(self, other_child)
if Symbol.debug_lookup:
Symbol.debug_indent -= 1
continue
our_child = None
if other_child.declaration is None:
if Symbol.debug_lookup:
Symbol.debug_print('no declaration in other child')
our_child = symbols[0]
else:
query_id = other_child.declaration.get_newest_id()
if Symbol.debug_lookup:
Symbol.debug_print('query_id: ', query_id)
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
our_child = symbol
continue
cand_id = symbol.declaration.get_newest_id()
if Symbol.debug_lookup:
Symbol.debug_print('candidate:', cand_id)
if cand_id == query_id:
our_child = symbol
break
if Symbol.debug_lookup:
Symbol.debug_indent -= 1
if our_child is None:
unconditional_add(self, other_child)
continue
if other_child.declaration and other_child.docname in docnames:
if not our_child.declaration:
our_child._fill_empty(
other_child.declaration, other_child.docname, other_child.line
)
elif our_child.docname != other_child.docname:
name = str(our_child.declaration)
msg = __(
'Duplicate C++ declaration, also defined at %s:%s.\n'
"Declaration is '.. cpp:%s:: %s'."
)
logger.warning(
msg,
our_child.docname,
our_child.line,
our_child.declaration.directiveType,
name,
location=(other_child.docname, other_child.line),
)
else:
our_object_type = our_child.declaration.objectType
other_object_type = other_child.declaration.objectType
our_child_parent_decl = our_child.parent.declaration
other_child_parent_decl = other_child.parent.declaration
if (
other_object_type == our_object_type
and other_object_type in {'templateParam', 'functionParam'}
and our_child_parent_decl == other_child_parent_decl
):
# `our_child` 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 += 'our_child:\n' + our_child.to_string(1)
msg += '\nother_child:\n' + other_child.to_string(1)
logger.warning(msg, location=other_child.docname)
our_child.merge_with(other_child, 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:
template_decls = templatePrefix.templates
else:
template_decls = []
res = self._add_symbols(
nestedName, template_decls, 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
nested_name = declaration.name
if declaration.templatePrefix:
template_decls = declaration.templatePrefix.templates
else:
template_decls = []
res = self._add_symbols(nested_name, template_decls, 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
if recurseInAnon:
children: Iterable[Symbol] = current.children_recurse_anon
else:
children = 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, template_params, 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:
ident_or_op = name.identOrOp
template_args = name.templateArgs
s = s._find_first_named_symbol(
ident_or_op,
template_params,
template_args,
template_shorthand=False,
match_self=False,
recurse_in_anon=False,
correct_primary_template_args=False,
)
if Symbol.debug_lookup:
Symbol.debug_print('name: ', name)
Symbol.debug_print('template_params:', template_params)
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 on_missing_qualified_symbol(
parent_symbol: Symbol,
ident_or_op: ASTIdentifier | ASTOperator,
template_params: Any,
template_args: ASTTemplateArgs,
) -> Symbol | None:
# TODO: Maybe search without template args?
# Though, the correct_primary_template_args does
# that for primary templates.
# Is there another case where it would be good?
if parent_symbol.declaration is not None:
if parent_symbol.declaration.objectType == 'templateParam':
raise QualifiedSymbolIsTemplateParam
return None
try:
lookup_result = self._symbol_lookup(
nestedName,
templateDecls,
on_missing_qualified_symbol,
strict_template_param_arg_lists=False,
ancestor_lookup_type=typ,
template_shorthand=templateShorthand,
match_self=matchSelf,
recurse_in_anon=recurseInAnon,
correct_primary_template_args=False,
search_in_siblings=searchInSiblings,
)
except QualifiedSymbolIsTemplateParam:
return None, 'templateParamInQualified'
if lookup_result 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(lookup_result.symbols)
if len(res) != 0:
if Symbol.debug_lookup:
Symbol.debug_indent -= 2
return res, None
if lookup_result.parentSymbol.declaration is not None:
if lookup_result.parentSymbol.declaration.objectType == 'templateParam':
return None, 'templateParamInQualified'
# try without template params and args
symbol = lookup_result.parentSymbol._find_first_named_symbol(
lookup_result.identOrOp,
None,
None,
template_shorthand=templateShorthand,
match_self=matchSelf,
recurse_in_anon=recurseInAnon,
correct_primary_template_args=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:')
nested_name = declaration.name
if declaration.templatePrefix:
template_decls = declaration.templatePrefix.templates
else:
template_decls = []
def on_missing_qualified_symbol(
parent_symbol: Symbol,
ident_or_op: ASTIdentifier | ASTOperator,
template_params: Any,
template_args: ASTTemplateArgs,
) -> Symbol | None:
return None
lookup_result = self._symbol_lookup(
nested_name,
template_decls,
on_missing_qualified_symbol,
strict_template_param_arg_lists=False,
ancestor_lookup_type=typ,
template_shorthand=templateShorthand,
match_self=matchSelf,
recurse_in_anon=recurseInAnon,
correct_primary_template_args=False,
search_in_siblings=False,
)
if Symbol.debug_lookup:
Symbol.debug_indent -= 1
if lookup_result is None:
return None
symbols = list(lookup_result.symbols)
if len(symbols) == 0:
return None
query_symbol = Symbol(
parent=lookup_result.parentSymbol,
identOrOp=lookup_result.identOrOp,
templateParams=lookup_result.templateParams,
templateArgs=lookup_result.templateArgs,
declaration=declaration,
docname='fakeDocnameForQuery',
line=42,
)
query_id = declaration.get_newest_id()
for symbol in symbols:
if symbol.declaration is None:
continue
cand_id = symbol.declaration.get_newest_id()
if cand_id == query_id:
query_symbol.remove()
return symbol
query_symbol.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),
])