summaryrefslogtreecommitdiffstats
path: root/sphinx/domains/cpp.py
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/domains/cpp.py')
-rw-r--r--sphinx/domains/cpp.py8233
1 files changed, 8233 insertions, 0 deletions
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
new file mode 100644
index 0000000..80920c6
--- /dev/null
+++ b/sphinx/domains/cpp.py
@@ -0,0 +1,8233 @@
+"""The C++ language domain."""
+
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING, Any, Callable, TypeVar
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+from sphinx import addnodes
+from sphinx.directives import ObjectDescription
+from sphinx.domains import Domain, ObjType
+from sphinx.errors import NoUri
+from sphinx.locale import _, __
+from sphinx.roles import SphinxRole, XRefRole
+from sphinx.transforms import SphinxTransform
+from sphinx.transforms.post_transforms import ReferencesResolver
+from sphinx.util import logging
+from sphinx.util.cfamily import (
+ ASTAttributeList,
+ ASTBaseBase,
+ ASTBaseParenExprList,
+ BaseParser,
+ DefinitionError,
+ NoOldIdError,
+ StringifyTransform,
+ UnsupportedMultiCharacterCharLiteral,
+ anon_identifier_re,
+ binary_literal_re,
+ char_literal_re,
+ float_literal_re,
+ float_literal_suffix_re,
+ hex_literal_re,
+ identifier_re,
+ integer_literal_re,
+ integers_literal_suffix_re,
+ octal_literal_re,
+ verify_description_mode,
+)
+from sphinx.util.docfields import Field, GroupedField
+from sphinx.util.docutils import SphinxDirective
+from sphinx.util.nodes import make_refnode
+
+if TYPE_CHECKING:
+ from collections.abc import Generator, Iterator
+
+ from docutils.nodes import Element, Node, TextElement, system_message
+
+ from sphinx.addnodes import desc_signature, pending_xref
+ from sphinx.application import Sphinx
+ from sphinx.builders import Builder
+ from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import OptionSpec
+
+logger = logging.getLogger(__name__)
+T = TypeVar('T')
+
+"""
+ Important note on ids
+ ----------------------------------------------------------------------------
+
+ Multiple id generation schemes are used due to backwards compatibility.
+ - v1: 1.2.3 <= version < 1.3
+ The style used before the rewrite.
+ It is not the actual old code, but a replication of the behaviour.
+ - v2: 1.3 <= version < now
+ Standardised mangling scheme from
+ https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
+ though not completely implemented.
+ All versions are generated and attached to elements. The newest is used for
+ the index. All of the versions should work as permalinks.
+
+
+ Signature Nodes and Tagnames
+ ----------------------------------------------------------------------------
+
+ Each signature is in a desc_signature node, where all children are
+ desc_signature_line nodes. Each of these lines will have the attribute
+ 'sphinx_line_type' set to one of the following (prioritized):
+ - 'declarator', if the line contains the name of the declared object.
+ - 'templateParams', if the line starts a template parameter list,
+ - 'templateParams', if the line has template parameters
+ Note: such lines might get a new tag in the future.
+ - 'templateIntroduction, if the line is on the form 'conceptName{...}'
+ No other desc_signature nodes should exist (so far).
+
+
+ Grammar
+ ----------------------------------------------------------------------------
+
+ See https://www.nongnu.org/hcb/ for the grammar,
+ and https://github.com/cplusplus/draft/blob/master/source/grammar.tex,
+ and https://github.com/cplusplus/concepts-ts
+ for the newest grammar.
+
+ common grammar things:
+ template-declaration ->
+ "template" "<" template-parameter-list ">" declaration
+ template-parameter-list ->
+ template-parameter
+ | template-parameter-list "," template-parameter
+ template-parameter ->
+ type-parameter
+ | parameter-declaration # i.e., same as a function argument
+
+ type-parameter ->
+ "class" "..."[opt] identifier[opt]
+ | "class" identifier[opt] "=" type-id
+ | "typename" "..."[opt] identifier[opt]
+ | "typename" identifier[opt] "=" type-id
+ | "template" "<" template-parameter-list ">"
+ "class" "..."[opt] identifier[opt]
+ | "template" "<" template-parameter-list ">"
+ "class" identifier[opt] "=" id-expression
+ # also, from C++17 we can have "typename" in template templates
+ templateDeclPrefix ->
+ "template" "<" template-parameter-list ">"
+
+ simple-declaration ->
+ attribute-specifier-seq[opt] decl-specifier-seq[opt]
+ init-declarator-list[opt] ;
+ # Make the semicolon optional.
+ # For now: drop the attributes (TODO).
+ # Use at most 1 init-declarator.
+ -> decl-specifier-seq init-declarator
+ -> decl-specifier-seq declarator initializer
+
+ decl-specifier ->
+ storage-class-specifier ->
+ ( "static" (only for member_object and function_object)
+ | "extern" (only for member_object and function_object)
+ | "register"
+ )
+ thread_local[opt] (only for member_object)
+ (it can also appear before the others)
+
+ | type-specifier -> trailing-type-specifier
+ | function-specifier -> "inline" | "virtual" | "explicit" (only
+ for function_object)
+ | "friend" (only for function_object)
+ | "constexpr" (only for member_object and function_object)
+ trailing-type-specifier ->
+ simple-type-specifier
+ | elaborated-type-specifier
+ | typename-specifier
+ | cv-qualifier -> "const" | "volatile"
+ stricter grammar for decl-specifier-seq (with everything, each object
+ uses a subset):
+ visibility storage-class-specifier function-specifier "friend"
+ "constexpr" "volatile" "const" trailing-type-specifier
+ # where trailing-type-specifier can no be cv-qualifier
+ # Inside e.g., template parameters a strict subset is used
+ # (see type-specifier-seq)
+ trailing-type-specifier ->
+ simple-type-specifier ->
+ ::[opt] nested-name-specifier[opt] type-name
+ | ::[opt] nested-name-specifier "template" simple-template-id
+ | "char" | "bool" | etc.
+ | decltype-specifier
+ | elaborated-type-specifier ->
+ class-key attribute-specifier-seq[opt] ::[opt]
+ nested-name-specifier[opt] identifier
+ | class-key ::[opt] nested-name-specifier[opt] template[opt]
+ simple-template-id
+ | "enum" ::[opt] nested-name-specifier[opt] identifier
+ | typename-specifier ->
+ "typename" ::[opt] nested-name-specifier identifier
+ | "typename" ::[opt] nested-name-specifier template[opt]
+ simple-template-id
+ class-key -> "class" | "struct" | "union"
+ type-name ->* identifier | simple-template-id
+ # ignoring attributes and decltype, and then some left-factoring
+ trailing-type-specifier ->
+ rest-of-trailing
+ ("class" | "struct" | "union" | "typename") rest-of-trailing
+ built-in -> "char" | "bool" | etc.
+ decltype-specifier
+ rest-of-trailing -> (with some simplification)
+ "::"[opt] list-of-elements-separated-by-::
+ element ->
+ "template"[opt] identifier ("<" template-argument-list ">")[opt]
+ template-argument-list ->
+ template-argument "..."[opt]
+ | template-argument-list "," template-argument "..."[opt]
+ template-argument ->
+ constant-expression
+ | type-specifier-seq abstract-declarator
+ | id-expression
+
+
+ declarator ->
+ ptr-declarator
+ | noptr-declarator parameters-and-qualifiers trailing-return-type
+ ptr-declarator ->
+ noptr-declarator
+ | ptr-operator ptr-declarator
+ noptr-declarator ->
+ declarator-id attribute-specifier-seq[opt] ->
+ "..."[opt] id-expression
+ | rest-of-trailing
+ | noptr-declarator parameters-and-qualifiers
+ | noptr-declarator "[" constant-expression[opt] "]"
+ attribute-specifier-seq[opt]
+ | "(" ptr-declarator ")"
+ ptr-operator ->
+ "*" attribute-specifier-seq[opt] cv-qualifier-seq[opt]
+ | "& attribute-specifier-seq[opt]
+ | "&&" attribute-specifier-seq[opt]
+ | "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
+ cv-qualifier-seq[opt]
+ # function_object must use a parameters-and-qualifiers, the others may
+ # use it (e.g., function pointers)
+ parameters-and-qualifiers ->
+ "(" parameter-clause ")" attribute-specifier-seq[opt]
+ cv-qualifier-seq[opt] ref-qualifier[opt]
+ exception-specification[opt]
+ ref-qualifier -> "&" | "&&"
+ exception-specification ->
+ "noexcept" ("(" constant-expression ")")[opt]
+ "throw" ("(" type-id-list ")")[opt]
+ # TODO: we don't implement attributes
+ # member functions can have initializers, but we fold them into here
+ memberFunctionInit -> "=" "0"
+ # (note: only "0" is allowed as the value, according to the standard,
+ # right?)
+
+ enum-head ->
+ enum-key attribute-specifier-seq[opt] nested-name-specifier[opt]
+ identifier enum-base[opt]
+ enum-key -> "enum" | "enum struct" | "enum class"
+ enum-base ->
+ ":" type
+ enumerator-definition ->
+ identifier
+ | identifier "=" constant-expression
+
+ We additionally add the possibility for specifying the visibility as the
+ first thing.
+
+ concept_object:
+ goal:
+ just a declaration of the name (for now)
+
+ grammar: only a single template parameter list, and the nested name
+ may not have any template argument lists
+
+ "template" "<" template-parameter-list ">"
+ nested-name-specifier
+
+ type_object:
+ goal:
+ either a single type (e.g., "MyClass:Something_T" or a typedef-like
+ thing (e.g. "Something Something_T" or "int I_arr[]"
+ grammar, single type: based on a type in a function parameter, but
+ without a name:
+ parameter-declaration
+ -> attribute-specifier-seq[opt] decl-specifier-seq
+ abstract-declarator[opt]
+ # Drop the attributes
+ -> decl-specifier-seq abstract-declarator[opt]
+ grammar, typedef-like: no initilizer
+ decl-specifier-seq declarator
+ Can start with a templateDeclPrefix.
+
+ member_object:
+ goal: as a type_object which must have a declarator, and optionally
+ with a initializer
+ grammar:
+ decl-specifier-seq declarator initializer
+ Can start with a templateDeclPrefix.
+
+ function_object:
+ goal: a function declaration, TODO: what about templates? for now: skip
+ grammar: no initializer
+ decl-specifier-seq declarator
+ Can start with a templateDeclPrefix.
+
+ class_object:
+ goal: a class declaration, but with specification of a base class
+ grammar:
+ attribute-specifier-seq[opt]
+ nested-name "final"[opt] (":" base-specifier-list)[opt]
+ base-specifier-list ->
+ base-specifier "..."[opt]
+ | base-specifier-list, base-specifier "..."[opt]
+ base-specifier ->
+ base-type-specifier
+ | "virtual" access-spe"cifier[opt] base-type-specifier
+ | access-specifier[opt] "virtual"[opt] base-type-specifier
+ Can start with a templateDeclPrefix.
+
+ enum_object:
+ goal: an unscoped enum or a scoped enum, optionally with the underlying
+ type specified
+ grammar:
+ ("class" | "struct")[opt] visibility[opt]
+ attribute-specifier-seq[opt] nested-name (":" type)[opt]
+ enumerator_object:
+ goal: an element in a scoped or unscoped enum. The name should be
+ injected according to the scopedness.
+ grammar:
+ nested-name ("=" constant-expression)
+
+ namespace_object:
+ goal: a directive to put all following declarations in a specific scope
+ grammar:
+ nested-name
+"""
+
+udl_identifier_re = re.compile(r'''
+ [a-zA-Z_][a-zA-Z0-9_]*\b # note, no word boundary in the beginning
+''', re.VERBOSE)
+_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
+_visibility_re = re.compile(r'\b(public|private|protected)\b')
+_operator_re = re.compile(r'''
+ \[\s*\]
+ | \(\s*\)
+ | \+\+ | --
+ | ->\*? | \,
+ | (<<|>>)=? | && | \|\|
+ | <=>
+ | [!<>=/*%+|&^~-]=?
+ | (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b)
+''', re.VERBOSE)
+_fold_operator_re = re.compile(r'''
+ ->\* | \.\* | \,
+ | (<<|>>)=? | && | \|\|
+ | !=
+ | [<>=/*%+|&^~-]=?
+''', re.VERBOSE)
+# see https://en.cppreference.com/w/cpp/keyword
+_keywords = [
+ 'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor',
+ 'bool', 'break', 'case', 'catch', 'char', 'char8_t', 'char16_t', 'char32_t',
+ 'class', 'compl', 'concept', 'const', 'consteval', 'constexpr', 'constinit',
+ 'const_cast', 'continue',
+ 'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else',
+ 'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
+ 'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new',
+ 'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq',
+ 'private', 'protected', 'public', 'register', 'reinterpret_cast',
+ 'requires', 'return', 'short', 'signed', 'sizeof', 'static',
+ 'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this',
+ 'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename',
+ 'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t',
+ 'while', 'xor', 'xor_eq',
+]
+
+
+_simple_type_specifiers_re = re.compile(r"""
+ \b(
+ auto|void|bool
+ |signed|unsigned
+ |short|long
+ |char|wchar_t|char(8|16|32)_t
+ |int
+ |__int(64|128) # extension
+ |float|double
+ |__float80|_Float64x|__float128|_Float128 # extension
+ |_Complex|_Imaginary # extension
+ )\b
+""", re.VERBOSE)
+
+_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
+# ------------------------------------------------------------------------------
+
+_id_fundamental_v1 = {
+ 'char': 'c',
+ 'signed char': 'c',
+ 'unsigned char': 'C',
+ 'int': 'i',
+ 'signed int': 'i',
+ 'unsigned int': 'U',
+ 'long': 'l',
+ 'signed long': 'l',
+ 'unsigned long': 'L',
+ 'bool': 'b',
+}
+_id_shorthands_v1 = {
+ 'std::string': 'ss',
+ 'std::ostream': 'os',
+ 'std::istream': 'is',
+ 'std::iostream': 'ios',
+ 'std::vector': 'v',
+ 'std::map': 'm',
+}
+_id_operator_v1 = {
+ 'new': 'new-operator',
+ 'new[]': 'new-array-operator',
+ 'delete': 'delete-operator',
+ 'delete[]': 'delete-array-operator',
+ # the arguments will make the difference between unary and binary
+ # '+(unary)' : 'ps',
+ # '-(unary)' : 'ng',
+ # '&(unary)' : 'ad',
+ # '*(unary)' : 'de',
+ '~': 'inv-operator',
+ '+': 'add-operator',
+ '-': 'sub-operator',
+ '*': 'mul-operator',
+ '/': 'div-operator',
+ '%': 'mod-operator',
+ '&': 'and-operator',
+ '|': 'or-operator',
+ '^': 'xor-operator',
+ '=': 'assign-operator',
+ '+=': 'add-assign-operator',
+ '-=': 'sub-assign-operator',
+ '*=': 'mul-assign-operator',
+ '/=': 'div-assign-operator',
+ '%=': 'mod-assign-operator',
+ '&=': 'and-assign-operator',
+ '|=': 'or-assign-operator',
+ '^=': 'xor-assign-operator',
+ '<<': 'lshift-operator',
+ '>>': 'rshift-operator',
+ '<<=': 'lshift-assign-operator',
+ '>>=': 'rshift-assign-operator',
+ '==': 'eq-operator',
+ '!=': 'neq-operator',
+ '<': 'lt-operator',
+ '>': 'gt-operator',
+ '<=': 'lte-operator',
+ '>=': 'gte-operator',
+ '!': 'not-operator',
+ '&&': 'sand-operator',
+ '||': 'sor-operator',
+ '++': 'inc-operator',
+ '--': 'dec-operator',
+ ',': 'comma-operator',
+ '->*': 'pointer-by-pointer-operator',
+ '->': 'pointer-operator',
+ '()': 'call-operator',
+ '[]': 'subscript-operator',
+}
+
+# ------------------------------------------------------------------------------
+# Id v > 1 constants
+# ------------------------------------------------------------------------------
+
+_id_fundamental_v2 = {
+ # not all of these are actually parsed as fundamental types, TODO: do that
+ 'void': 'v',
+ 'bool': 'b',
+ 'char': 'c',
+ 'signed char': 'a',
+ 'unsigned char': 'h',
+ 'wchar_t': 'w',
+ 'char32_t': 'Di',
+ 'char16_t': 'Ds',
+ 'char8_t': 'Du',
+ 'short': 's',
+ 'short int': 's',
+ 'signed short': 's',
+ 'signed short int': 's',
+ 'unsigned short': 't',
+ 'unsigned short int': 't',
+ 'int': 'i',
+ 'signed': 'i',
+ 'signed int': 'i',
+ 'unsigned': 'j',
+ 'unsigned int': 'j',
+ 'long': 'l',
+ 'long int': 'l',
+ 'signed long': 'l',
+ 'signed long int': 'l',
+ 'unsigned long': 'm',
+ 'unsigned long int': 'm',
+ 'long long': 'x',
+ 'long long int': 'x',
+ 'signed long long': 'x',
+ 'signed long long int': 'x',
+ '__int64': 'x',
+ 'unsigned long long': 'y',
+ 'unsigned long long int': 'y',
+ '__int128': 'n',
+ 'signed __int128': 'n',
+ 'unsigned __int128': 'o',
+ 'float': 'f',
+ 'double': 'd',
+ 'long double': 'e',
+ '__float80': 'e', '_Float64x': 'e',
+ '__float128': 'g', '_Float128': 'g',
+ '_Complex float': 'Cf',
+ '_Complex double': 'Cd',
+ '_Complex long double': 'Ce',
+ '_Imaginary float': 'f',
+ '_Imaginary double': 'd',
+ '_Imaginary long double': 'e',
+ 'auto': 'Da',
+ 'decltype(auto)': 'Dc',
+ 'std::nullptr_t': 'Dn',
+}
+_id_operator_v2 = {
+ 'new': 'nw',
+ 'new[]': 'na',
+ 'delete': 'dl',
+ 'delete[]': 'da',
+ # the arguments will make the difference between unary and binary
+ # in operator definitions
+ # '+(unary)' : 'ps',
+ # '-(unary)' : 'ng',
+ # '&(unary)' : 'ad',
+ # '*(unary)' : 'de',
+ '~': 'co', 'compl': 'co',
+ '+': 'pl',
+ '-': 'mi',
+ '*': 'ml',
+ '/': 'dv',
+ '%': 'rm',
+ '&': 'an', 'bitand': 'an',
+ '|': 'or', 'bitor': 'or',
+ '^': 'eo', 'xor': 'eo',
+ '=': 'aS',
+ '+=': 'pL',
+ '-=': 'mI',
+ '*=': 'mL',
+ '/=': 'dV',
+ '%=': 'rM',
+ '&=': 'aN', 'and_eq': 'aN',
+ '|=': 'oR', 'or_eq': 'oR',
+ '^=': 'eO', 'xor_eq': 'eO',
+ '<<': 'ls',
+ '>>': 'rs',
+ '<<=': 'lS',
+ '>>=': 'rS',
+ '==': 'eq',
+ '!=': 'ne', 'not_eq': 'ne',
+ '<': 'lt',
+ '>': 'gt',
+ '<=': 'le',
+ '>=': 'ge',
+ '<=>': 'ss',
+ '!': 'nt', 'not': 'nt',
+ '&&': 'aa', 'and': 'aa',
+ '||': 'oo', 'or': 'oo',
+ '++': 'pp',
+ '--': 'mm',
+ ',': 'cm',
+ '->*': 'pm',
+ '->': 'pt',
+ '()': 'cl',
+ '[]': 'ix',
+ '.*': 'ds', # this one is not overloadable, but we need it for expressions
+ '?': 'qu',
+}
+_id_operator_unary_v2 = {
+ '++': 'pp_',
+ '--': 'mm_',
+ '*': 'de',
+ '&': 'ad',
+ '+': 'ps',
+ '-': 'ng',
+ '!': 'nt', 'not': 'nt',
+ '~': 'co', 'compl': 'co',
+}
+_id_char_from_prefix: dict[str | None, str] = {
+ None: 'c', 'u8': 'c',
+ 'u': 'Ds', 'U': 'Di', 'L': 'w',
+}
+# these are ordered by preceedence
+_expression_bin_ops = [
+ ['||', 'or'],
+ ['&&', 'and'],
+ ['|', 'bitor'],
+ ['^', 'xor'],
+ ['&', 'bitand'],
+ ['==', '!=', 'not_eq'],
+ ['<=>', '<=', '>=', '<', '>'],
+ ['<<', '>>'],
+ ['+', '-'],
+ ['*', '/', '%'],
+ ['.*', '->*'],
+]
+_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
+_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
+ ">>=", "<<=", "&=", "and_eq", "^=", "|=", "xor_eq", "or_eq"]
+_id_explicit_cast = {
+ 'dynamic_cast': 'dc',
+ 'static_cast': 'sc',
+ 'const_cast': 'cc',
+ 'reinterpret_cast': 'rc',
+}
+
+
+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 ASTBase(ASTBaseBase):
+ pass
+
+
+# Names
+################################################################################
+
+class ASTIdentifier(ASTBase):
+ def __init__(self, identifier: str) -> None:
+ assert identifier is not None
+ assert len(identifier) != 0
+ self.identifier = identifier
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.identifier)
+
+ def is_anon(self) -> bool:
+ return self.identifier[0] == '@'
+
+ def get_id(self, version: int) -> str:
+ if self.is_anon() and version < 3:
+ raise NoOldIdError
+ if version == 1:
+ if self.identifier == 'size_t':
+ return 's'
+ else:
+ return self.identifier
+ if self.identifier == "std":
+ return 'St'
+ elif self.identifier[0] == "~":
+ # a destructor, just use an arbitrary version of dtors
+ return 'D0'
+ else:
+ if self.is_anon():
+ return 'Ut%d_%s' % (len(self.identifier) - 1, self.identifier[1:])
+ else:
+ return str(len(self.identifier)) + self.identifier
+
+ # and this is where we finally make a difference between __str__ and the display string
+
+ def __str__(self) -> str:
+ return self.identifier
+
+ def get_display_string(self) -> str:
+ return "[anonymous]" if self.is_anon() else self.identifier
+
+ def describe_signature(self, signode: TextElement, mode: str, env: BuildEnvironment,
+ prefix: str, templateArgs: str, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.is_anon():
+ node = addnodes.desc_sig_name(text="[anonymous]")
+ else:
+ node = addnodes.desc_sig_name(self.identifier, self.identifier)
+ if mode == 'markType':
+ targetText = prefix + self.identifier + templateArgs
+ pnode = addnodes.pending_xref('', refdomain='cpp',
+ reftype='identifier',
+ reftarget=targetText, modname=None,
+ classname=None)
+ pnode['cpp:parent_key'] = symbol.get_lookup_key()
+ pnode += node
+ signode += pnode
+ elif mode == 'lastIsName':
+ nameNode = addnodes.desc_name()
+ nameNode += node
+ signode += nameNode
+ elif mode == 'noneIsName':
+ signode += node
+ elif mode == 'param':
+ node['classes'].append('sig-param')
+ signode += node
+ elif mode == 'udl':
+ # the target is 'operator""id' instead of just 'id'
+ assert len(prefix) == 0
+ assert len(templateArgs) == 0
+ assert not self.is_anon()
+ targetText = 'operator""' + self.identifier
+ pnode = addnodes.pending_xref('', refdomain='cpp',
+ reftype='identifier',
+ reftarget=targetText, modname=None,
+ classname=None)
+ pnode['cpp:parent_key'] = symbol.get_lookup_key()
+ pnode += node
+ signode += pnode
+ else:
+ raise Exception('Unknown description mode: %s' % mode)
+
+
+class ASTNestedNameElement(ASTBase):
+ def __init__(self, identOrOp: ASTIdentifier | ASTOperator,
+ templateArgs: ASTTemplateArgs) -> None:
+ self.identOrOp = identOrOp
+ self.templateArgs = templateArgs
+
+ def is_operator(self) -> bool:
+ return False
+
+ def get_id(self, version: int) -> str:
+ res = self.identOrOp.get_id(version)
+ if self.templateArgs:
+ res += self.templateArgs.get_id(version)
+ return res
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.identOrOp)
+ if self.templateArgs:
+ res += transform(self.templateArgs)
+ return res
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, prefix: str, symbol: Symbol) -> None:
+ tArgs = str(self.templateArgs) if self.templateArgs is not None else ''
+ self.identOrOp.describe_signature(signode, mode, env, prefix, tArgs, symbol)
+ if self.templateArgs is not None:
+ self.templateArgs.describe_signature(signode, 'markType', env, symbol)
+
+
+class ASTNestedName(ASTBase):
+ def __init__(self, names: list[ASTNestedNameElement],
+ templates: list[bool], rooted: bool) -> None:
+ assert len(names) > 0
+ self.names = names
+ self.templates = templates
+ assert len(self.names) == len(self.templates)
+ self.rooted = rooted
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self
+
+ def num_templates(self) -> int:
+ count = 0
+ for n in self.names:
+ if n.is_operator():
+ continue
+ if n.templateArgs:
+ count += 1
+ return count
+
+ def get_id(self, version: int, modifiers: str = '') -> str:
+ if version == 1:
+ tt = str(self)
+ if tt in _id_shorthands_v1:
+ return _id_shorthands_v1[tt]
+ else:
+ return '::'.join(n.get_id(version) for n in self.names)
+
+ res = []
+ if len(self.names) > 1 or len(modifiers) > 0:
+ res.append('N')
+ res.append(modifiers)
+ for n in self.names:
+ res.append(n.get_id(version))
+ if len(self.names) > 1 or len(modifiers) > 0:
+ res.append('E')
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.rooted:
+ res.append('')
+ for i in range(len(self.names)):
+ n = self.names[i]
+ if self.templates[i]:
+ res.append("template " + transform(n))
+ else:
+ res.append(transform(n))
+ return '::'.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ # just print the name part, with template args, not template params
+ if mode == 'noneIsName':
+ if self.rooted:
+ unreachable = "Can this happen?"
+ raise AssertionError(unreachable) # TODO
+ signode += nodes.Text('::')
+ for i in range(len(self.names)):
+ if i != 0:
+ unreachable = "Can this happen?"
+ raise AssertionError(unreachable) # TODO
+ signode += nodes.Text('::blah')
+ n = self.names[i]
+ if self.templates[i]:
+ unreachable = "Can this happen?"
+ raise AssertionError(unreachable) # TODO
+ signode += nodes.Text("template")
+ signode += nodes.Text(" ")
+ n.describe_signature(signode, mode, env, '', symbol)
+ elif mode == 'param':
+ assert not self.rooted, str(self)
+ assert len(self.names) == 1
+ assert not self.templates[0]
+ self.names[0].describe_signature(signode, 'param', env, '', symbol)
+ elif mode in ('markType', 'lastIsName', 'markName'):
+ # Each element should be a pending xref targeting the complete
+ # prefix. however, only the identifier part should be a link, such
+ # that template args can be a link as well.
+ # For 'lastIsName' we should also prepend template parameter lists.
+ templateParams: list[Any] = []
+ if mode == 'lastIsName':
+ assert symbol is not None
+ if symbol.declaration.templatePrefix is not None:
+ templateParams = symbol.declaration.templatePrefix.templates
+ iTemplateParams = 0
+ templateParamsPrefix = ''
+ prefix = ''
+ first = True
+ names = self.names[:-1] if mode == 'lastIsName' else self.names
+ # If lastIsName, then wrap all of the prefix in a desc_addname,
+ # else append directly to signode.
+ # NOTE: Breathe previously relied on the prefix being in the desc_addname node,
+ # so it can remove it in inner declarations.
+ dest = signode
+ if mode == 'lastIsName':
+ dest = addnodes.desc_addname()
+ if self.rooted:
+ prefix += '::'
+ if mode == 'lastIsName' and len(names) == 0:
+ signode += addnodes.desc_sig_punctuation('::', '::')
+ else:
+ dest += addnodes.desc_sig_punctuation('::', '::')
+ for i in range(len(names)):
+ nne = names[i]
+ template = self.templates[i]
+ if not first:
+ dest += addnodes.desc_sig_punctuation('::', '::')
+ prefix += '::'
+ if template:
+ dest += addnodes.desc_sig_keyword('template', 'template')
+ dest += addnodes.desc_sig_space()
+ first = False
+ txt_nne = str(nne)
+ if txt_nne != '':
+ if nne.templateArgs and iTemplateParams < len(templateParams):
+ templateParamsPrefix += str(templateParams[iTemplateParams])
+ iTemplateParams += 1
+ nne.describe_signature(dest, 'markType',
+ env, templateParamsPrefix + prefix, symbol)
+ prefix += txt_nne
+ if mode == 'lastIsName':
+ if len(self.names) > 1:
+ dest += addnodes.desc_sig_punctuation('::', '::')
+ signode += dest
+ if self.templates[-1]:
+ signode += addnodes.desc_sig_keyword('template', 'template')
+ signode += addnodes.desc_sig_space()
+ self.names[-1].describe_signature(signode, mode, env, '', symbol)
+ else:
+ raise Exception('Unknown description mode: %s' % mode)
+
+
+################################################################################
+# Expressions
+################################################################################
+
+class ASTExpression(ASTBase):
+ def get_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+
+# Primary expressions
+################################################################################
+
+class ASTLiteral(ASTExpression):
+ pass
+
+
+class ASTPointerLiteral(ASTLiteral):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'nullptr'
+
+ def get_id(self, version: int) -> str:
+ return 'LDnE'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('nullptr', 'nullptr')
+
+
+class ASTBooleanLiteral(ASTLiteral):
+ def __init__(self, value: bool) -> None:
+ self.value = value
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.value:
+ return 'true'
+ else:
+ return 'false'
+
+ def get_id(self, version: int) -> str:
+ if self.value:
+ return 'L1E'
+ else:
+ return 'L0E'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword(str(self), str(self))
+
+
+class ASTNumberLiteral(ASTLiteral):
+ def __init__(self, data: str) -> None:
+ self.data = data
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.data
+
+ def get_id(self, version: int) -> str:
+ # TODO: floats should be mangled by writing the hex of the binary representation
+ return "L%sE" % self.data.replace("'", "")
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_literal_number(self.data, self.data)
+
+
+class ASTStringLiteral(ASTLiteral):
+ def __init__(self, data: str) -> None:
+ self.data = data
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.data
+
+ def get_id(self, version: int) -> str:
+ # note: the length is not really correct with escaping
+ return "LA%d_KcE" % (len(self.data) - 2)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_literal_string(self.data, self.data)
+
+
+class ASTCharLiteral(ASTLiteral):
+ def __init__(self, prefix: str, data: str) -> None:
+ self.prefix = prefix # may be None when no prefix
+ self.data = data
+ assert prefix in _id_char_from_prefix
+ self.type = _id_char_from_prefix[prefix]
+ decoded = data.encode().decode('unicode-escape')
+ if len(decoded) == 1:
+ self.value = ord(decoded)
+ else:
+ raise UnsupportedMultiCharacterCharLiteral(decoded)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.prefix is None:
+ return "'" + self.data + "'"
+ else:
+ return self.prefix + "'" + self.data + "'"
+
+ def get_id(self, version: int) -> str:
+ # TODO: the ID should be have L E around it
+ return self.type + str(self.value)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.prefix is not None:
+ signode += addnodes.desc_sig_keyword(self.prefix, self.prefix)
+ txt = "'" + self.data + "'"
+ signode += addnodes.desc_sig_literal_char(txt, txt)
+
+
+class ASTUserDefinedLiteral(ASTLiteral):
+ def __init__(self, literal: ASTLiteral, ident: ASTIdentifier):
+ self.literal = literal
+ self.ident = ident
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.literal) + transform(self.ident)
+
+ def get_id(self, version: int) -> str:
+ # mangle as if it was a function call: ident(literal)
+ return f'clL_Zli{self.ident.get_id(version)}E{self.literal.get_id(version)}E'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.literal.describe_signature(signode, mode, env, symbol)
+ self.ident.describe_signature(signode, "udl", env, "", "", symbol)
+
+
+################################################################################
+
+class ASTThisLiteral(ASTExpression):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "this"
+
+ def get_id(self, version: int) -> str:
+ return "fpT"
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('this', 'this')
+
+
+class ASTFoldExpr(ASTExpression):
+ def __init__(self, leftExpr: ASTExpression,
+ op: str, rightExpr: ASTExpression) -> None:
+ assert leftExpr is not None or rightExpr is not None
+ self.leftExpr = leftExpr
+ self.op = op
+ self.rightExpr = rightExpr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['(']
+ if self.leftExpr:
+ res.append(transform(self.leftExpr))
+ res.append(' ')
+ res.append(self.op)
+ res.append(' ')
+ res.append('...')
+ if self.rightExpr:
+ res.append(' ')
+ res.append(self.op)
+ res.append(' ')
+ res.append(transform(self.rightExpr))
+ res.append(')')
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ assert version >= 3
+ if version == 3:
+ return str(self)
+ # https://github.com/itanium-cxx-abi/cxx-abi/pull/67
+ res = []
+ if self.leftExpr is None: # (... op expr)
+ res.append('fl')
+ elif self.rightExpr is None: # (expr op ...)
+ res.append('fr')
+ else: # (expr op ... op expr)
+ # we don't check where the parameter pack is,
+ # we just always call this a binary left fold
+ res.append('fL')
+ res.append(_id_operator_v2[self.op])
+ if self.leftExpr:
+ res.append(self.leftExpr.get_id(version))
+ if self.rightExpr:
+ res.append(self.rightExpr.get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ if self.leftExpr:
+ self.leftExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_operator(self.op, self.op)
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ if self.rightExpr:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_operator(self.op, self.op)
+ signode += addnodes.desc_sig_space()
+ self.rightExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTParenExpr(ASTExpression):
+ def __init__(self, expr: ASTExpression):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '(' + transform(self.expr) + ')'
+
+ def get_id(self, version: int) -> str:
+ return self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTIdExpression(ASTExpression):
+ def __init__(self, name: ASTNestedName):
+ # note: this class is basically to cast a nested name as an expression
+ self.name = name
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.name)
+
+ def get_id(self, version: int) -> str:
+ return self.name.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.name.describe_signature(signode, mode, env, symbol)
+
+
+# Postfix expressions
+################################################################################
+
+class ASTPostfixOp(ASTBase):
+ def get_id(self, idPrefix: str, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+
+class ASTPostfixArray(ASTPostfixOp):
+ def __init__(self, expr: ASTExpression):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '[' + transform(self.expr) + ']'
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ return 'ix' + idPrefix + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_punctuation('[', '[')
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(']', ']')
+
+
+class ASTPostfixMember(ASTPostfixOp):
+ def __init__(self, name: ASTNestedName):
+ self.name = name
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '.' + transform(self.name)
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ return 'dt' + idPrefix + self.name.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_punctuation('.', '.')
+ self.name.describe_signature(signode, 'noneIsName', env, symbol)
+
+
+class ASTPostfixMemberOfPointer(ASTPostfixOp):
+ def __init__(self, name: ASTNestedName):
+ self.name = name
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '->' + transform(self.name)
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ return 'pt' + idPrefix + self.name.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_operator('->', '->')
+ self.name.describe_signature(signode, 'noneIsName', env, symbol)
+
+
+class ASTPostfixInc(ASTPostfixOp):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '++'
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ return 'pp' + idPrefix
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_operator('++', '++')
+
+
+class ASTPostfixDec(ASTPostfixOp):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '--'
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ return 'mm' + idPrefix
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_operator('--', '--')
+
+
+class ASTPostfixCallExpr(ASTPostfixOp):
+ def __init__(self, lst: ASTParenExprList | ASTBracedInitList) -> None:
+ self.lst = lst
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.lst)
+
+ def get_id(self, idPrefix: str, version: int) -> str:
+ res = ['cl', idPrefix]
+ for e in self.lst.exprs:
+ res.append(e.get_id(version))
+ res.append('E')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.lst.describe_signature(signode, mode, env, symbol)
+
+
+class ASTPostfixExpr(ASTExpression):
+ def __init__(self, prefix: ASTType, postFixes: list[ASTPostfixOp]):
+ self.prefix = prefix
+ self.postFixes = postFixes
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = [transform(self.prefix)]
+ for p in self.postFixes:
+ res.append(transform(p))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ id = self.prefix.get_id(version)
+ for p in self.postFixes:
+ id = p.get_id(id, version)
+ return id
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.prefix.describe_signature(signode, mode, env, symbol)
+ for p in self.postFixes:
+ p.describe_signature(signode, mode, env, symbol)
+
+
+class ASTExplicitCast(ASTExpression):
+ def __init__(self, cast: str, typ: ASTType, expr: ASTExpression):
+ assert cast in _id_explicit_cast
+ self.cast = cast
+ self.typ = typ
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = [self.cast]
+ res.append('<')
+ res.append(transform(self.typ))
+ res.append('>(')
+ res.append(transform(self.expr))
+ res.append(')')
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ return (_id_explicit_cast[self.cast] +
+ self.typ.get_id(version) +
+ self.expr.get_id(version))
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword(self.cast, self.cast)
+ signode += addnodes.desc_sig_punctuation('<', '<')
+ self.typ.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation('>', '>')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTTypeId(ASTExpression):
+ def __init__(self, typeOrExpr: ASTType | ASTExpression, isType: bool):
+ self.typeOrExpr = typeOrExpr
+ self.isType = isType
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'typeid(' + transform(self.typeOrExpr) + ')'
+
+ def get_id(self, version: int) -> str:
+ prefix = 'ti' if self.isType else 'te'
+ return prefix + self.typeOrExpr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('typeid', 'typeid')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.typeOrExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+# Unary expressions
+################################################################################
+
+class ASTUnaryOpExpr(ASTExpression):
+ def __init__(self, op: str, expr: ASTExpression):
+ self.op = op
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.op[0] in 'cn':
+ return self.op + " " + transform(self.expr)
+ else:
+ return self.op + transform(self.expr)
+
+ def get_id(self, version: int) -> str:
+ return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.op[0] in 'cn':
+ signode += addnodes.desc_sig_keyword(self.op, self.op)
+ signode += addnodes.desc_sig_space()
+ else:
+ signode += addnodes.desc_sig_operator(self.op, self.op)
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
+class ASTSizeofParamPack(ASTExpression):
+ def __init__(self, identifier: ASTIdentifier):
+ self.identifier = identifier
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "sizeof...(" + transform(self.identifier) + ")"
+
+ def get_id(self, version: int) -> str:
+ return 'sZ' + self.identifier.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('sizeof', 'sizeof')
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.identifier.describe_signature(signode, 'markType', env,
+ symbol=symbol, prefix="", templateArgs="")
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTSizeofType(ASTExpression):
+ def __init__(self, typ: ASTType):
+ self.typ = typ
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "sizeof(" + transform(self.typ) + ")"
+
+ def get_id(self, version: int) -> str:
+ return 'st' + self.typ.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('sizeof', 'sizeof')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.typ.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTSizeofExpr(ASTExpression):
+ def __init__(self, expr: ASTExpression):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "sizeof " + transform(self.expr)
+
+ def get_id(self, version: int) -> str:
+ return 'sz' + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('sizeof', 'sizeof')
+ signode += addnodes.desc_sig_space()
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
+class ASTAlignofExpr(ASTExpression):
+ def __init__(self, typ: ASTType):
+ self.typ = typ
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "alignof(" + transform(self.typ) + ")"
+
+ def get_id(self, version: int) -> str:
+ return 'at' + self.typ.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('alignof', 'alignof')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.typ.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTNoexceptExpr(ASTExpression):
+ def __init__(self, expr: ASTExpression):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'noexcept(' + transform(self.expr) + ')'
+
+ def get_id(self, version: int) -> str:
+ return 'nx' + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('noexcept', 'noexcept')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTNewExpr(ASTExpression):
+ def __init__(self, rooted: bool, isNewTypeId: bool, typ: ASTType,
+ initList: ASTParenExprList | ASTBracedInitList) -> None:
+ self.rooted = rooted
+ self.isNewTypeId = isNewTypeId
+ self.typ = typ
+ self.initList = initList
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.rooted:
+ res.append('::')
+ res.append('new ')
+ # TODO: placement
+ if self.isNewTypeId:
+ res.append(transform(self.typ))
+ else:
+ raise AssertionError
+ if self.initList is not None:
+ res.append(transform(self.initList))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ # the array part will be in the type mangling, so na is not used
+ res = ['nw']
+ # TODO: placement
+ res.append('_')
+ res.append(self.typ.get_id(version))
+ if self.initList is not None:
+ res.append(self.initList.get_id(version))
+ else:
+ res.append('E')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.rooted:
+ signode += addnodes.desc_sig_punctuation('::', '::')
+ signode += addnodes.desc_sig_keyword('new', 'new')
+ signode += addnodes.desc_sig_space()
+ # TODO: placement
+ if self.isNewTypeId:
+ self.typ.describe_signature(signode, mode, env, symbol)
+ else:
+ raise AssertionError
+ if self.initList is not None:
+ self.initList.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeleteExpr(ASTExpression):
+ def __init__(self, rooted: bool, array: bool, expr: ASTExpression):
+ self.rooted = rooted
+ self.array = array
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.rooted:
+ res.append('::')
+ res.append('delete ')
+ if self.array:
+ res.append('[] ')
+ res.append(transform(self.expr))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ if self.array:
+ id = "da"
+ else:
+ id = "dl"
+ return id + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.rooted:
+ signode += addnodes.desc_sig_punctuation('::', '::')
+ signode += addnodes.desc_sig_keyword('delete', 'delete')
+ signode += addnodes.desc_sig_space()
+ if self.array:
+ signode += addnodes.desc_sig_punctuation('[]', '[]')
+ signode += addnodes.desc_sig_space()
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
+# Other expressions
+################################################################################
+
+class ASTCastExpr(ASTExpression):
+ def __init__(self, typ: ASTType, expr: ASTExpression):
+ self.typ = typ
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['(']
+ res.append(transform(self.typ))
+ res.append(')')
+ res.append(transform(self.expr))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ return 'cv' + self.typ.get_id(version) + self.expr.get_id(version)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.typ.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
+class ASTBinOpExpr(ASTExpression):
+ def __init__(self, exprs: list[ASTExpression], ops: list[str]):
+ assert len(exprs) > 0
+ assert len(exprs) == len(ops) + 1
+ self.exprs = exprs
+ self.ops = ops
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.exprs[0]))
+ for i in range(1, len(self.exprs)):
+ res.append(' ')
+ res.append(self.ops[i - 1])
+ res.append(' ')
+ res.append(transform(self.exprs[i]))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ assert version >= 2
+ res = []
+ for i in range(len(self.ops)):
+ res.append(_id_operator_v2[self.ops[i]])
+ res.append(self.exprs[i].get_id(version))
+ res.append(self.exprs[-1].get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.exprs[0].describe_signature(signode, mode, env, symbol)
+ for i in range(1, len(self.exprs)):
+ signode += addnodes.desc_sig_space()
+ op = self.ops[i - 1]
+ if ord(op[0]) >= ord('a') and ord(op[0]) <= ord('z'):
+ signode += addnodes.desc_sig_keyword(op, op)
+ else:
+ signode += addnodes.desc_sig_operator(op, op)
+ signode += addnodes.desc_sig_space()
+ self.exprs[i].describe_signature(signode, mode, env, symbol)
+
+
+class ASTConditionalExpr(ASTExpression):
+ def __init__(self, ifExpr: ASTExpression, thenExpr: ASTExpression,
+ elseExpr: ASTExpression):
+ self.ifExpr = ifExpr
+ self.thenExpr = thenExpr
+ self.elseExpr = elseExpr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.ifExpr))
+ res.append(' ? ')
+ res.append(transform(self.thenExpr))
+ res.append(' : ')
+ res.append(transform(self.elseExpr))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ assert version >= 2
+ res = []
+ res.append(_id_operator_v2['?'])
+ res.append(self.ifExpr.get_id(version))
+ res.append(self.thenExpr.get_id(version))
+ res.append(self.elseExpr.get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.ifExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_operator('?', '?')
+ signode += addnodes.desc_sig_space()
+ self.thenExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_operator(':', ':')
+ signode += addnodes.desc_sig_space()
+ self.elseExpr.describe_signature(signode, mode, env, symbol)
+
+
+class ASTBracedInitList(ASTBase):
+ def __init__(self, exprs: list[ASTExpression | ASTBracedInitList],
+ trailingComma: bool) -> None:
+ self.exprs = exprs
+ self.trailingComma = trailingComma
+
+ def get_id(self, version: int) -> str:
+ return "il%sE" % ''.join(e.get_id(version) for e in self.exprs)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ exprs = ', '.join(transform(e) for e in self.exprs)
+ trailingComma = ',' if self.trailingComma else ''
+ return f'{{{exprs}{trailingComma}}}'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('{', '{')
+ first = True
+ for e in self.exprs:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ else:
+ first = False
+ e.describe_signature(signode, mode, env, symbol)
+ if self.trailingComma:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_punctuation('}', '}')
+
+
+class ASTAssignmentExpr(ASTExpression):
+ def __init__(self, leftExpr: ASTExpression, op: str,
+ rightExpr: ASTExpression | ASTBracedInitList):
+ self.leftExpr = leftExpr
+ self.op = op
+ self.rightExpr = rightExpr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.leftExpr))
+ res.append(' ')
+ res.append(self.op)
+ res.append(' ')
+ res.append(transform(self.rightExpr))
+ return ''.join(res)
+
+ def get_id(self, version: int) -> str:
+ # we end up generating the ID from left to right, instead of right to left
+ res = []
+ res.append(_id_operator_v2[self.op])
+ res.append(self.leftExpr.get_id(version))
+ res.append(self.rightExpr.get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.leftExpr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_space()
+ if ord(self.op[0]) >= ord('a') and ord(self.op[0]) <= ord('z'):
+ signode += addnodes.desc_sig_keyword(self.op, self.op)
+ else:
+ signode += addnodes.desc_sig_operator(self.op, self.op)
+ signode += addnodes.desc_sig_space()
+ self.rightExpr.describe_signature(signode, mode, env, symbol)
+
+
+class ASTCommaExpr(ASTExpression):
+ def __init__(self, exprs: list[ASTExpression]):
+ assert len(exprs) > 0
+ self.exprs = exprs
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ', '.join(transform(e) for e in self.exprs)
+
+ def get_id(self, version: int) -> str:
+ id_ = _id_operator_v2[',']
+ res = []
+ for i in range(len(self.exprs) - 1):
+ res.append(id_)
+ res.append(self.exprs[i].get_id(version))
+ res.append(self.exprs[-1].get_id(version))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.exprs[0].describe_signature(signode, mode, env, symbol)
+ for i in range(1, len(self.exprs)):
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ self.exprs[i].describe_signature(signode, mode, env, symbol)
+
+
+class ASTFallbackExpr(ASTExpression):
+ def __init__(self, expr: str):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.expr
+
+ def get_id(self, version: int) -> str:
+ return str(self.expr)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += nodes.literal(self.expr, self.expr)
+
+
+################################################################################
+# Types
+################################################################################
+
+# Things for ASTNestedName
+################################################################################
+
+class ASTOperator(ASTBase):
+ def is_anon(self) -> bool:
+ return False
+
+ def is_operator(self) -> bool:
+ return True
+
+ def get_id(self, version: int) -> str:
+ raise NotImplementedError
+
+ def _describe_identifier(self, signode: TextElement, identnode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ """Render the prefix into signode, and the last part into identnode."""
+ raise NotImplementedError
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, prefix: str, templateArgs: str,
+ symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if mode == 'lastIsName':
+ mainName = addnodes.desc_name()
+ self._describe_identifier(mainName, mainName, env, symbol)
+ signode += mainName
+ elif mode == 'markType':
+ targetText = prefix + str(self) + templateArgs
+ pnode = addnodes.pending_xref('', refdomain='cpp',
+ reftype='identifier',
+ reftarget=targetText, modname=None,
+ classname=None)
+ pnode['cpp:parent_key'] = symbol.get_lookup_key()
+ # Render the identifier part, but collapse it into a string
+ # and make that the a link to this operator.
+ # E.g., if it is 'operator SomeType', then 'SomeType' becomes
+ # a link to the operator, not to 'SomeType'.
+ container = nodes.literal()
+ self._describe_identifier(signode, container, env, symbol)
+ txt = container.astext()
+ pnode += addnodes.desc_name(txt, txt)
+ signode += pnode
+ else:
+ addName = addnodes.desc_addname()
+ self._describe_identifier(addName, addName, env, symbol)
+ signode += addName
+
+
+class ASTOperatorBuildIn(ASTOperator):
+ def __init__(self, op: str) -> None:
+ self.op = op
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ ids = _id_operator_v1
+ if self.op not in ids:
+ raise NoOldIdError
+ else:
+ ids = _id_operator_v2
+ if self.op not in ids:
+ raise Exception('Internal error: Built-in operator "%s" can not '
+ 'be mapped to an id.' % self.op)
+ return ids[self.op]
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox":
+ return 'operator ' + self.op
+ else:
+ return 'operator' + self.op
+
+ def _describe_identifier(self, signode: TextElement, identnode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('operator', 'operator')
+ if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox":
+ signode += addnodes.desc_sig_space()
+ identnode += addnodes.desc_sig_operator(self.op, self.op)
+
+
+class ASTOperatorLiteral(ASTOperator):
+ def __init__(self, identifier: ASTIdentifier) -> None:
+ self.identifier = identifier
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return 'li' + self.identifier.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'operator""' + transform(self.identifier)
+
+ def _describe_identifier(self, signode: TextElement, identnode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('operator', 'operator')
+ signode += addnodes.desc_sig_literal_string('""', '""')
+ self.identifier.describe_signature(identnode, 'markType', env, '', '', symbol)
+
+
+class ASTOperatorType(ASTOperator):
+ def __init__(self, type: ASTType) -> None:
+ self.type = type
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ return 'castto-%s-operator' % self.type.get_id(version)
+ else:
+ return 'cv' + self.type.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ''.join(['operator ', transform(self.type)])
+
+ def get_name_no_template(self) -> str:
+ return str(self)
+
+ def _describe_identifier(self, signode: TextElement, identnode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('operator', 'operator')
+ signode += addnodes.desc_sig_space()
+ self.type.describe_signature(identnode, 'markType', env, symbol)
+
+
+class ASTTemplateArgConstant(ASTBase):
+ def __init__(self, value: ASTExpression) -> None:
+ self.value = value
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.value)
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ return str(self).replace(' ', '-')
+ if version == 2:
+ return 'X' + str(self) + 'E'
+ return 'X' + self.value.get_id(version) + 'E'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.value.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTemplateArgs(ASTBase):
+ def __init__(self, args: list[ASTType | ASTTemplateArgConstant],
+ packExpansion: bool) -> None:
+ assert args is not None
+ self.args = args
+ self.packExpansion = packExpansion
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ res = []
+ res.append(':')
+ res.append('.'.join(a.get_id(version) for a in self.args))
+ res.append(':')
+ return ''.join(res)
+
+ res = []
+ res.append('I')
+ if len(self.args) > 0:
+ for a in self.args[:-1]:
+ res.append(a.get_id(version))
+ if self.packExpansion:
+ res.append('J')
+ res.append(self.args[-1].get_id(version))
+ if self.packExpansion:
+ res.append('E')
+ res.append('E')
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ', '.join(transform(a) for a in self.args)
+ if self.packExpansion:
+ res += '...'
+ return '<' + res + '>'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('<', '<')
+ first = True
+ for a in self.args:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ first = False
+ a.describe_signature(signode, 'markType', env, symbol=symbol)
+ if self.packExpansion:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ signode += addnodes.desc_sig_punctuation('>', '>')
+
+
+# Main part of declarations
+################################################################################
+
+class ASTTrailingTypeSpec(ASTBase):
+ def get_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+
+class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
+ def __init__(self, names: list[str], canonNames: list[str]) -> None:
+ assert len(names) != 0
+ assert len(names) == len(canonNames), (names, canonNames)
+ self.names = names
+ # the canonical name list is for ID lookup
+ self.canonNames = canonNames
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ' '.join(self.names)
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ res = []
+ for a in self.canonNames:
+ if a in _id_fundamental_v1:
+ res.append(_id_fundamental_v1[a])
+ else:
+ res.append(a)
+ return '-'.join(res)
+
+ txt = ' '.join(self.canonNames)
+ if txt not in _id_fundamental_v2:
+ raise Exception(
+ 'Semi-internal error: Fundamental type "%s" can not be mapped '
+ 'to an ID. Is it a true fundamental type? If not so, the '
+ 'parser should have rejected it.' % txt)
+ return _id_fundamental_v2[txt]
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ first = True
+ for n in self.names:
+ if not first:
+ signode += addnodes.desc_sig_space()
+ else:
+ first = False
+ signode += addnodes.desc_sig_keyword_type(n, n)
+
+
+class ASTTrailingTypeSpecDecltypeAuto(ASTTrailingTypeSpec):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'decltype(auto)'
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return 'Dc'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('decltype', 'decltype')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ signode += addnodes.desc_sig_keyword('auto', 'auto')
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTTrailingTypeSpecDecltype(ASTTrailingTypeSpec):
+ def __init__(self, expr: ASTExpression):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'decltype(' + transform(self.expr) + ')'
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return 'DT' + self.expr.get_id(version) + "E"
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('decltype', 'decltype')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
+ def __init__(self, prefix: str, nestedName: ASTNestedName,
+ placeholderType: str | None) -> None:
+ self.prefix = prefix
+ self.nestedName = nestedName
+ self.placeholderType = placeholderType
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.nestedName
+
+ def get_id(self, version: int) -> str:
+ return self.nestedName.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.prefix:
+ res.append(self.prefix)
+ res.append(' ')
+ res.append(transform(self.nestedName))
+ if self.placeholderType is not None:
+ res.append(' ')
+ res.append(self.placeholderType)
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.prefix:
+ signode += addnodes.desc_sig_keyword(self.prefix, self.prefix)
+ signode += addnodes.desc_sig_space()
+ self.nestedName.describe_signature(signode, mode, env, symbol=symbol)
+ if self.placeholderType is not None:
+ signode += addnodes.desc_sig_space()
+ if self.placeholderType == 'auto':
+ signode += addnodes.desc_sig_keyword('auto', 'auto')
+ elif self.placeholderType == 'decltype(auto)':
+ signode += addnodes.desc_sig_keyword('decltype', 'decltype')
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ signode += addnodes.desc_sig_keyword('auto', 'auto')
+ signode += addnodes.desc_sig_punctuation(')', ')')
+ else:
+ raise AssertionError(self.placeholderType)
+
+
+class ASTFunctionParameter(ASTBase):
+ def __init__(self, arg: ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit,
+ ellipsis: bool = False) -> None:
+ self.arg = arg
+ self.ellipsis = ellipsis
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ # this is not part of the normal name mangling in C++
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=False)
+ # else, do the usual
+ if self.ellipsis:
+ return 'z'
+ else:
+ return self.arg.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.ellipsis:
+ return '...'
+ else:
+ return transform(self.arg)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.ellipsis:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ else:
+ self.arg.describe_signature(signode, mode, env, symbol=symbol)
+
+
+class ASTNoexceptSpec(ASTBase):
+ def __init__(self, expr: ASTExpression | None):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.expr:
+ return 'noexcept(' + transform(self.expr) + ')'
+ return 'noexcept'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('noexcept', 'noexcept')
+ if self.expr:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, 'markType', env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTParametersQualifiers(ASTBase):
+ def __init__(self, args: list[ASTFunctionParameter], volatile: bool, const: bool,
+ refQual: str | None, exceptionSpec: ASTNoexceptSpec,
+ trailingReturn: ASTType,
+ override: bool, final: bool, attrs: ASTAttributeList,
+ initializer: str | None) -> None:
+ self.args = args
+ self.volatile = volatile
+ self.const = const
+ self.refQual = refQual
+ self.exceptionSpec = exceptionSpec
+ self.trailingReturn = trailingReturn
+ self.override = override
+ self.final = final
+ self.attrs = attrs
+ self.initializer = initializer
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.args
+
+ def get_modifiers_id(self, version: int) -> str:
+ res = []
+ if self.volatile:
+ res.append('V')
+ if self.const:
+ if version == 1:
+ res.append('C')
+ else:
+ res.append('K')
+ if self.refQual == '&&':
+ res.append('O')
+ elif self.refQual == '&':
+ res.append('R')
+ return ''.join(res)
+
+ def get_param_id(self, version: int) -> str:
+ if version == 1:
+ if len(self.args) == 0:
+ return ''
+ else:
+ return '__' + '.'.join(a.get_id(version) for a in self.args)
+ if len(self.args) == 0:
+ return 'v'
+ else:
+ return ''.join(a.get_id(version) for a in self.args)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append('(')
+ first = True
+ for a in self.args:
+ if not first:
+ res.append(', ')
+ first = False
+ res.append(str(a))
+ res.append(')')
+ if self.volatile:
+ res.append(' volatile')
+ if self.const:
+ res.append(' const')
+ if self.refQual:
+ res.append(' ')
+ res.append(self.refQual)
+ if self.exceptionSpec:
+ res.append(' ')
+ res.append(transform(self.exceptionSpec))
+ if self.trailingReturn:
+ res.append(' -> ')
+ res.append(transform(self.trailingReturn))
+ if self.final:
+ res.append(' final')
+ if self.override:
+ res.append(' override')
+ if len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.attrs))
+ if self.initializer:
+ res.append(' = ')
+ res.append(self.initializer)
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ multi_line_parameter_list = False
+ test_node: Element = signode
+ while test_node.parent:
+ if not isinstance(test_node, addnodes.desc_signature):
+ test_node = test_node.parent
+ continue
+ multi_line_parameter_list = test_node.get('multi_line_parameter_list', False)
+ break
+
+ # only use the desc_parameterlist for the outer list, not for inner lists
+ if mode == 'lastIsName':
+ paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
+ for arg in self.args:
+ param = addnodes.desc_parameter('', '', noemph=True)
+ arg.describe_signature(param, 'param', env, symbol=symbol)
+ paramlist += param
+ signode += paramlist
+ else:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ first = True
+ for arg in self.args:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ first = False
+ arg.describe_signature(signode, 'markType', env, symbol=symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+ def _add_anno(signode: TextElement, text: str) -> None:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_keyword(text, text)
+
+ if self.volatile:
+ _add_anno(signode, 'volatile')
+ if self.const:
+ _add_anno(signode, 'const')
+ if self.refQual:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation(self.refQual, self.refQual)
+ if self.exceptionSpec:
+ signode += addnodes.desc_sig_space()
+ self.exceptionSpec.describe_signature(signode, mode, env, symbol)
+ if self.trailingReturn:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_operator('->', '->')
+ signode += addnodes.desc_sig_space()
+ self.trailingReturn.describe_signature(signode, mode, env, symbol)
+ if self.final:
+ _add_anno(signode, 'final')
+ if self.override:
+ _add_anno(signode, 'override')
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.attrs.describe_signature(signode)
+ if self.initializer:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ signode += addnodes.desc_sig_space()
+ assert self.initializer in ('0', 'delete', 'default')
+ if self.initializer == '0':
+ signode += addnodes.desc_sig_literal_number('0', '0')
+ else:
+ signode += addnodes.desc_sig_keyword(self.initializer, self.initializer)
+
+
+class ASTExplicitSpec(ASTBase):
+ def __init__(self, expr: ASTExpression | None) -> None:
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['explicit']
+ if self.expr is not None:
+ res.append('(')
+ res.append(transform(self.expr))
+ res.append(')')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('explicit', 'explicit')
+ if self.expr is not None:
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.expr.describe_signature(signode, 'markType', env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTDeclSpecsSimple(ASTBase):
+ def __init__(self, storage: str, threadLocal: bool, inline: bool, virtual: bool,
+ explicitSpec: ASTExplicitSpec | None,
+ consteval: bool, constexpr: bool, constinit: bool,
+ volatile: bool, const: bool, friend: bool,
+ attrs: ASTAttributeList) -> None:
+ self.storage = storage
+ self.threadLocal = threadLocal
+ self.inline = inline
+ self.virtual = virtual
+ self.explicitSpec = explicitSpec
+ self.consteval = consteval
+ self.constexpr = constexpr
+ self.constinit = constinit
+ self.volatile = volatile
+ self.const = const
+ self.friend = friend
+ self.attrs = attrs
+
+ def mergeWith(self, other: ASTDeclSpecsSimple) -> ASTDeclSpecsSimple:
+ if not other:
+ return self
+ return ASTDeclSpecsSimple(self.storage or other.storage,
+ self.threadLocal or other.threadLocal,
+ self.inline or other.inline,
+ self.virtual or other.virtual,
+ self.explicitSpec or other.explicitSpec,
+ self.consteval or other.consteval,
+ self.constexpr or other.constexpr,
+ self.constinit or other.constinit,
+ self.volatile or other.volatile,
+ self.const or other.const,
+ self.friend or other.friend,
+ self.attrs + other.attrs)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res: list[str] = []
+ if len(self.attrs) != 0:
+ res.append(transform(self.attrs))
+ if self.storage:
+ res.append(self.storage)
+ if self.threadLocal:
+ res.append('thread_local')
+ if self.inline:
+ res.append('inline')
+ if self.friend:
+ res.append('friend')
+ if self.virtual:
+ res.append('virtual')
+ if self.explicitSpec:
+ res.append(transform(self.explicitSpec))
+ if self.consteval:
+ res.append('consteval')
+ if self.constexpr:
+ res.append('constexpr')
+ if self.constinit:
+ res.append('constinit')
+ if self.volatile:
+ res.append('volatile')
+ if self.const:
+ res.append('const')
+ return ' '.join(res)
+
+ def describe_signature(self, signode: TextElement,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.attrs.describe_signature(signode)
+ addSpace = len(self.attrs) != 0
+
+ def _add(signode: TextElement, text: str) -> bool:
+ if addSpace:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_keyword(text, text)
+ return True
+
+ if self.storage:
+ addSpace = _add(signode, self.storage)
+ if self.threadLocal:
+ addSpace = _add(signode, 'thread_local')
+ if self.inline:
+ addSpace = _add(signode, 'inline')
+ if self.friend:
+ addSpace = _add(signode, 'friend')
+ if self.virtual:
+ addSpace = _add(signode, 'virtual')
+ if self.explicitSpec:
+ if addSpace:
+ signode += addnodes.desc_sig_space()
+ self.explicitSpec.describe_signature(signode, env, symbol)
+ addSpace = True
+ if self.consteval:
+ addSpace = _add(signode, 'consteval')
+ if self.constexpr:
+ addSpace = _add(signode, 'constexpr')
+ if self.constinit:
+ addSpace = _add(signode, 'constinit')
+ if self.volatile:
+ addSpace = _add(signode, 'volatile')
+ if self.const:
+ addSpace = _add(signode, 'const')
+
+
+class ASTDeclSpecs(ASTBase):
+ def __init__(self, outer: str,
+ leftSpecs: ASTDeclSpecsSimple, rightSpecs: ASTDeclSpecsSimple,
+ trailing: ASTTrailingTypeSpec) -> None:
+ # leftSpecs and rightSpecs are used for output
+ # allSpecs are used for id generation
+ self.outer = outer
+ self.leftSpecs = leftSpecs
+ self.rightSpecs = rightSpecs
+ self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
+ self.trailingTypeSpec = trailing
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ res = []
+ res.append(self.trailingTypeSpec.get_id(version))
+ if self.allSpecs.volatile:
+ res.append('V')
+ if self.allSpecs.const:
+ res.append('C')
+ return ''.join(res)
+ res = []
+ if self.allSpecs.volatile:
+ res.append('V')
+ if self.allSpecs.const:
+ res.append('K')
+ if self.trailingTypeSpec is not None:
+ res.append(self.trailingTypeSpec.get_id(version))
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res: list[str] = []
+ l = transform(self.leftSpecs)
+ if len(l) > 0:
+ res.append(l)
+ if self.trailingTypeSpec:
+ if len(res) > 0:
+ res.append(" ")
+ res.append(transform(self.trailingTypeSpec))
+ r = str(self.rightSpecs)
+ if len(r) > 0:
+ if len(res) > 0:
+ res.append(" ")
+ res.append(r)
+ return "".join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ numChildren = len(signode)
+ self.leftSpecs.describe_signature(signode, env, symbol)
+ addSpace = len(signode) != numChildren
+
+ if self.trailingTypeSpec:
+ if addSpace:
+ signode += addnodes.desc_sig_space()
+ numChildren = len(signode)
+ self.trailingTypeSpec.describe_signature(signode, mode, env,
+ symbol=symbol)
+ addSpace = len(signode) != numChildren
+
+ if len(str(self.rightSpecs)) > 0:
+ if addSpace:
+ signode += addnodes.desc_sig_space()
+ self.rightSpecs.describe_signature(signode, env, symbol)
+
+
+# Declarator
+################################################################################
+
+class ASTArray(ASTBase):
+ def __init__(self, size: ASTExpression):
+ self.size = size
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.size:
+ return '[' + transform(self.size) + ']'
+ else:
+ return '[]'
+
+ def get_id(self, version: int) -> str:
+ if version == 1:
+ return 'A'
+ if version == 2:
+ if self.size:
+ return 'A' + str(self.size) + '_'
+ else:
+ return 'A_'
+ if self.size:
+ return 'A' + self.size.get_id(version) + '_'
+ else:
+ return 'A_'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('[', '[')
+ if self.size:
+ self.size.describe_signature(signode, 'markType', env, symbol)
+ signode += addnodes.desc_sig_punctuation(']', ']')
+
+
+class ASTDeclarator(ASTBase):
+ @property
+ def name(self) -> ASTNestedName:
+ raise NotImplementedError(repr(self))
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ raise NotImplementedError(repr(self))
+
+ @property
+ def isPack(self) -> bool:
+ raise NotImplementedError(repr(self))
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ raise NotImplementedError(repr(self))
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ raise NotImplementedError(repr(self))
+
+ def require_space_after_declSpecs(self) -> bool:
+ raise NotImplementedError(repr(self))
+
+ def get_modifiers_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def get_param_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ raise NotImplementedError(repr(self))
+
+ def is_function_type(self) -> bool:
+ raise NotImplementedError(repr(self))
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+
+class ASTDeclaratorNameParamQual(ASTDeclarator):
+ def __init__(self, declId: ASTNestedName,
+ arrayOps: list[ASTArray],
+ paramQual: ASTParametersQualifiers) -> None:
+ self.declId = declId
+ self.arrayOps = arrayOps
+ self.paramQual = paramQual
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.declId
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.declId = name
+
+ @property
+ def isPack(self) -> bool:
+ return False
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.paramQual.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.paramQual.trailingReturn
+
+ # only the modifiers for a function, e.g.,
+ def get_modifiers_id(self, version: int) -> str:
+ # cv-qualifiers
+ if self.paramQual:
+ return self.paramQual.get_modifiers_id(version)
+ raise Exception("This should only be called on a function: %s" % self)
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ if self.paramQual:
+ return self.paramQual.get_param_id(version)
+ else:
+ return ''
+
+ def get_ptr_suffix_id(self, version: int) -> str: # only the array specifiers
+ return ''.join(a.get_id(version) for a in self.arrayOps)
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ assert version >= 2
+ res = []
+ # TODO: can we actually have both array ops and paramQual?
+ res.append(self.get_ptr_suffix_id(version))
+ if self.paramQual:
+ res.append(self.get_modifiers_id(version))
+ res.append('F')
+ res.append(returnTypeId)
+ res.append(self.get_param_id(version))
+ res.append('E')
+ else:
+ res.append(returnTypeId)
+ return ''.join(res)
+
+ # ------------------------------------------------------------------------
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.declId is not None
+
+ def is_function_type(self) -> bool:
+ return self.paramQual is not None
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.declId:
+ res.append(transform(self.declId))
+ for op in self.arrayOps:
+ res.append(transform(op))
+ if self.paramQual:
+ res.append(transform(self.paramQual))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.declId:
+ self.declId.describe_signature(signode, mode, env, symbol)
+ for op in self.arrayOps:
+ op.describe_signature(signode, mode, env, symbol)
+ if self.paramQual:
+ self.paramQual.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorNameBitField(ASTDeclarator):
+ def __init__(self, declId: ASTNestedName, size: ASTExpression):
+ self.declId = declId
+ self.size = size
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.declId
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.declId = name
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ return ''
+
+ def get_ptr_suffix_id(self, version: int) -> str: # only the array specifiers
+ return ''
+
+ # ------------------------------------------------------------------------
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.declId is not None
+
+ def is_function_type(self) -> bool:
+ return False
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.declId:
+ res.append(transform(self.declId))
+ res.append(" : ")
+ res.append(transform(self.size))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.declId:
+ self.declId.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation(':', ':')
+ signode += addnodes.desc_sig_space()
+ self.size.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorPtr(ASTDeclarator):
+ def __init__(self, next: ASTDeclarator, volatile: bool, const: bool,
+ attrs: ASTAttributeList) -> None:
+ assert next
+ self.next = next
+ self.volatile = volatile
+ self.const = const
+ self.attrs = attrs
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.next.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
+ @property
+ def isPack(self) -> bool:
+ return self.next.isPack
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.next.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.next.trailingReturn
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.next.require_space_after_declSpecs()
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['*']
+ res.append(transform(self.attrs))
+ if len(self.attrs) != 0 and (self.volatile or self.const):
+ res.append(' ')
+ if self.volatile:
+ res.append('volatile')
+ if self.const:
+ if self.volatile:
+ res.append(' ')
+ res.append('const')
+ if self.const or self.volatile or len(self.attrs) > 0:
+ if self.next.require_space_after_declSpecs():
+ res.append(' ')
+ res.append(transform(self.next))
+ return ''.join(res)
+
+ def get_modifiers_id(self, version: int) -> str:
+ return self.next.get_modifiers_id(version)
+
+ def get_param_id(self, version: int) -> str:
+ return self.next.get_param_id(version)
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ if version == 1:
+ res = ['P']
+ if self.volatile:
+ res.append('V')
+ if self.const:
+ res.append('C')
+ res.append(self.next.get_ptr_suffix_id(version))
+ return ''.join(res)
+
+ res = [self.next.get_ptr_suffix_id(version)]
+ res.append('P')
+ if self.volatile:
+ res.append('V')
+ if self.const:
+ res.append('C')
+ return ''.join(res)
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ # ReturnType *next, so we are part of the return type of 'next
+ res = ['P']
+ if self.volatile:
+ res.append('V')
+ if self.const:
+ res.append('C')
+ res.append(returnTypeId)
+ return self.next.get_type_id(version, returnTypeId=''.join(res))
+
+ def is_function_type(self) -> bool:
+ return self.next.is_function_type()
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('*', '*')
+ self.attrs.describe_signature(signode)
+ if len(self.attrs) != 0 and (self.volatile or self.const):
+ signode += addnodes.desc_sig_space()
+
+ def _add_anno(signode: TextElement, text: str) -> None:
+ signode += addnodes.desc_sig_keyword(text, text)
+ if self.volatile:
+ _add_anno(signode, 'volatile')
+ if self.const:
+ if self.volatile:
+ signode += addnodes.desc_sig_space()
+ _add_anno(signode, 'const')
+ if self.const or self.volatile or len(self.attrs) > 0:
+ if self.next.require_space_after_declSpecs():
+ signode += addnodes.desc_sig_space()
+ self.next.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorRef(ASTDeclarator):
+ def __init__(self, next: ASTDeclarator, attrs: ASTAttributeList) -> None:
+ assert next
+ self.next = next
+ self.attrs = attrs
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.next.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
+ @property
+ def isPack(self) -> bool:
+ return self.next.isPack
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.next.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.next.trailingReturn
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.next.require_space_after_declSpecs()
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['&']
+ res.append(transform(self.attrs))
+ if len(self.attrs) != 0 and self.next.require_space_after_declSpecs():
+ res.append(' ')
+ res.append(transform(self.next))
+ return ''.join(res)
+
+ def get_modifiers_id(self, version: int) -> str:
+ return self.next.get_modifiers_id(version)
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ return self.next.get_param_id(version)
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ if version == 1:
+ return 'R' + self.next.get_ptr_suffix_id(version)
+ else:
+ return self.next.get_ptr_suffix_id(version) + 'R'
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ assert version >= 2
+ # ReturnType &next, so we are part of the return type of 'next
+ return self.next.get_type_id(version, returnTypeId='R' + returnTypeId)
+
+ def is_function_type(self) -> bool:
+ return self.next.is_function_type()
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('&', '&')
+ self.attrs.describe_signature(signode)
+ if len(self.attrs) > 0 and self.next.require_space_after_declSpecs():
+ signode += addnodes.desc_sig_space()
+ self.next.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorParamPack(ASTDeclarator):
+ def __init__(self, next: ASTDeclarator) -> None:
+ assert next
+ self.next = next
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.next.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.next.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.next.trailingReturn
+
+ @property
+ def isPack(self) -> bool:
+ return True
+
+ def require_space_after_declSpecs(self) -> bool:
+ return False
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.next)
+ if self.next.name:
+ res = ' ' + res
+ return '...' + res
+
+ def get_modifiers_id(self, version: int) -> str:
+ return self.next.get_modifiers_id(version)
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ return self.next.get_param_id(version)
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ if version == 1:
+ return 'Dp' + self.next.get_ptr_suffix_id(version)
+ else:
+ return self.next.get_ptr_suffix_id(version) + 'Dp'
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ assert version >= 2
+ # ReturnType... next, so we are part of the return type of 'next
+ return self.next.get_type_id(version, returnTypeId='Dp' + returnTypeId)
+
+ def is_function_type(self) -> bool:
+ return self.next.is_function_type()
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ if self.next.name:
+ signode += addnodes.desc_sig_space()
+ self.next.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorMemPtr(ASTDeclarator):
+ def __init__(self, className: ASTNestedName,
+ const: bool, volatile: bool, next: ASTDeclarator) -> None:
+ assert className
+ assert next
+ self.className = className
+ self.const = const
+ self.volatile = volatile
+ self.next = next
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.next.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.next.name = name
+
+ @property
+ def isPack(self):
+ return self.next.isPack
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.next.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.next.trailingReturn
+
+ def require_space_after_declSpecs(self) -> bool:
+ return True
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.className))
+ res.append('::*')
+ if self.volatile:
+ res.append('volatile')
+ if self.const:
+ if self.volatile:
+ res.append(' ')
+ res.append('const')
+ if self.next.require_space_after_declSpecs():
+ res.append(' ')
+ res.append(transform(self.next))
+ return ''.join(res)
+
+ def get_modifiers_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return self.next.get_modifiers_id(version)
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ if version == 1:
+ raise NoOldIdError
+ return self.next.get_param_id(version)
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError
+ raise NotImplementedError
+ return self.next.get_ptr_suffix_id(version) + 'Dp'
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ assert version >= 2
+ # ReturnType name::* next, so we are part of the return type of next
+ nextReturnTypeId = ''
+ if self.volatile:
+ nextReturnTypeId += 'V'
+ if self.const:
+ nextReturnTypeId += 'K'
+ nextReturnTypeId += 'M'
+ nextReturnTypeId += self.className.get_id(version)
+ nextReturnTypeId += returnTypeId
+ return self.next.get_type_id(version, nextReturnTypeId)
+
+ def is_function_type(self) -> bool:
+ return self.next.is_function_type()
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.className.describe_signature(signode, 'markType', env, symbol)
+ signode += addnodes.desc_sig_punctuation('::', '::')
+ signode += addnodes.desc_sig_punctuation('*', '*')
+
+ def _add_anno(signode: TextElement, text: str) -> None:
+ signode += addnodes.desc_sig_keyword(text, text)
+ if self.volatile:
+ _add_anno(signode, 'volatile')
+ if self.const:
+ if self.volatile:
+ signode += addnodes.desc_sig_space()
+ _add_anno(signode, 'const')
+ if self.next.require_space_after_declSpecs():
+ signode += addnodes.desc_sig_space()
+ self.next.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorParen(ASTDeclarator):
+ def __init__(self, inner: ASTDeclarator, next: ASTDeclarator) -> None:
+ assert inner
+ assert next
+ self.inner = inner
+ self.next = next
+ # TODO: we assume the name, params, and qualifiers are in inner
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.inner.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.inner.name = name
+
+ @property
+ def isPack(self):
+ return self.inner.isPack or self.next.isPack
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.inner.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.inner.trailingReturn
+
+ def require_space_after_declSpecs(self) -> bool:
+ return True
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = ['(']
+ res.append(transform(self.inner))
+ res.append(')')
+ res.append(transform(self.next))
+ return ''.join(res)
+
+ def get_modifiers_id(self, version: int) -> str:
+ return self.inner.get_modifiers_id(version)
+
+ def get_param_id(self, version: int) -> str: # only the parameters (if any)
+ return self.inner.get_param_id(version)
+
+ def get_ptr_suffix_id(self, version: int) -> str:
+ if version == 1:
+ raise NoOldIdError # TODO: was this implemented before?
+ return self.next.get_ptr_suffix_id(version) + \
+ self.inner.get_ptr_suffix_id(version)
+ return self.inner.get_ptr_suffix_id(version) + \
+ self.next.get_ptr_suffix_id(version)
+
+ def get_type_id(self, version: int, returnTypeId: str) -> str:
+ assert version >= 2
+ # ReturnType (inner)next, so 'inner' returns everything outside
+ nextId = self.next.get_type_id(version, returnTypeId)
+ return self.inner.get_type_id(version, returnTypeId=nextId)
+
+ def is_function_type(self) -> bool:
+ return self.inner.is_function_type()
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ self.inner.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+ self.next.describe_signature(signode, "noneIsName", env, symbol)
+
+
+# Type and initializer stuff
+##############################################################################################
+
+class ASTPackExpansionExpr(ASTExpression):
+ def __init__(self, expr: ASTExpression | ASTBracedInitList):
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.expr) + '...'
+
+ def get_id(self, version: int) -> str:
+ id = self.expr.get_id(version)
+ return 'sp' + id
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.expr.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation('...', '...')
+
+
+class ASTParenExprList(ASTBaseParenExprList):
+ def __init__(self, exprs: list[ASTExpression | ASTBracedInitList]) -> None:
+ self.exprs = exprs
+
+ def get_id(self, version: int) -> str:
+ return "pi%sE" % ''.join(e.get_id(version) for e in self.exprs)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ exprs = [transform(e) for e in self.exprs]
+ return '(%s)' % ', '.join(exprs)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('(', '(')
+ first = True
+ for e in self.exprs:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ else:
+ first = False
+ e.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation(')', ')')
+
+
+class ASTInitializer(ASTBase):
+ def __init__(self, value: ASTExpression | ASTBracedInitList,
+ hasAssign: bool = True) -> None:
+ self.value = value
+ self.hasAssign = hasAssign
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ val = transform(self.value)
+ if self.hasAssign:
+ return ' = ' + val
+ else:
+ return val
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.hasAssign:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ signode += addnodes.desc_sig_space()
+ self.value.describe_signature(signode, 'markType', env, symbol)
+
+
+class ASTType(ASTBase):
+ def __init__(self, declSpecs: ASTDeclSpecs, decl: ASTDeclarator) -> None:
+ assert declSpecs
+ assert decl
+ self.declSpecs = declSpecs
+ self.decl = decl
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.decl.name
+
+ @name.setter
+ def name(self, name: ASTNestedName) -> None:
+ self.decl.name = name
+
+ @property
+ def isPack(self) -> bool:
+ return self.decl.isPack
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.decl.function_params
+
+ @property
+ def trailingReturn(self) -> ASTType:
+ return self.decl.trailingReturn
+
+ def get_id(self, version: int, objectType: str | None = None,
+ symbol: Symbol | None = None) -> str:
+ if version == 1:
+ res = []
+ if objectType: # needs the name
+ if objectType == 'function': # also modifiers
+ res.append(symbol.get_full_nested_name().get_id(version))
+ res.append(self.decl.get_param_id(version))
+ res.append(self.decl.get_modifiers_id(version))
+ if (self.declSpecs.leftSpecs.constexpr or
+ (self.declSpecs.rightSpecs and
+ self.declSpecs.rightSpecs.constexpr)):
+ res.append('CE')
+ elif objectType == 'type': # just the name
+ res.append(symbol.get_full_nested_name().get_id(version))
+ else:
+ raise AssertionError(objectType)
+ else: # only type encoding
+ if self.decl.is_function_type():
+ raise NoOldIdError
+ res.append(self.declSpecs.get_id(version))
+ res.append(self.decl.get_ptr_suffix_id(version))
+ res.append(self.decl.get_param_id(version))
+ return ''.join(res)
+ # other versions
+ res = []
+ if objectType: # needs the name
+ if objectType == 'function': # also modifiers
+ modifiers = self.decl.get_modifiers_id(version)
+ res.append(symbol.get_full_nested_name().get_id(version, modifiers))
+ if version >= 4:
+ # with templates we need to mangle the return type in as well
+ templ = symbol.declaration.templatePrefix
+ if templ is not None:
+ typeId = self.decl.get_ptr_suffix_id(version)
+ if self.trailingReturn:
+ returnTypeId = self.trailingReturn.get_id(version)
+ else:
+ returnTypeId = self.declSpecs.get_id(version)
+ res.append(typeId)
+ res.append(returnTypeId)
+ res.append(self.decl.get_param_id(version))
+ elif objectType == 'type': # just the name
+ res.append(symbol.get_full_nested_name().get_id(version))
+ else:
+ raise AssertionError(objectType)
+ else: # only type encoding
+ # the 'returnType' of a non-function type is simply just the last
+ # type, i.e., for 'int*' it is 'int'
+ returnTypeId = self.declSpecs.get_id(version)
+ typeId = self.decl.get_type_id(version, returnTypeId)
+ res.append(typeId)
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ declSpecs = transform(self.declSpecs)
+ res.append(declSpecs)
+ if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0:
+ res.append(' ')
+ res.append(transform(self.decl))
+ return ''.join(res)
+
+ def get_type_declaration_prefix(self) -> str:
+ if self.declSpecs.trailingTypeSpec:
+ return 'typedef'
+ else:
+ return 'type'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.declSpecs.describe_signature(signode, 'markType', env, symbol)
+ if (self.decl.require_space_after_declSpecs() and
+ len(str(self.declSpecs)) > 0):
+ signode += addnodes.desc_sig_space()
+ # for parameters that don't really declare new names we get 'markType',
+ # this should not be propagated, but be 'noneIsName'.
+ if mode == 'markType':
+ mode = 'noneIsName'
+ self.decl.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTemplateParamConstrainedTypeWithInit(ASTBase):
+ def __init__(self, type: ASTType, init: ASTType) -> None:
+ assert type
+ self.type = type
+ self.init = init
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.type.name
+
+ @property
+ def isPack(self) -> bool:
+ return self.type.isPack
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ # this is not part of the normal name mangling in C++
+ assert version >= 2
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=False)
+ else:
+ return self.type.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.type)
+ if self.init:
+ res += " = "
+ res += transform(self.init)
+ return res
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.type.describe_signature(signode, mode, env, symbol)
+ if self.init:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ signode += addnodes.desc_sig_space()
+ self.init.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTypeWithInit(ASTBase):
+ def __init__(self, type: ASTType, init: ASTInitializer) -> None:
+ self.type = type
+ self.init = init
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.type.name
+
+ @property
+ def isPack(self) -> bool:
+ return self.type.isPack
+
+ def get_id(self, version: int, objectType: str | None = None,
+ symbol: Symbol | None = None) -> str:
+ if objectType != 'member':
+ return self.type.get_id(version, objectType)
+ if version == 1:
+ return (symbol.get_full_nested_name().get_id(version) + '__' +
+ self.type.get_id(version))
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.type))
+ if self.init:
+ res.append(transform(self.init))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.type.describe_signature(signode, mode, env, symbol)
+ if self.init:
+ self.init.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTypeUsing(ASTBase):
+ def __init__(self, name: ASTNestedName, type: ASTType) -> None:
+ self.name = name
+ self.type = type
+
+ def get_id(self, version: int, objectType: str | None = None,
+ symbol: Symbol | None = None) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.name))
+ if self.type:
+ res.append(' = ')
+ res.append(transform(self.type))
+ return ''.join(res)
+
+ def get_type_declaration_prefix(self) -> str:
+ return 'using'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.name.describe_signature(signode, mode, env, symbol=symbol)
+ if self.type:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ signode += addnodes.desc_sig_space()
+ self.type.describe_signature(signode, 'markType', env, symbol=symbol)
+
+
+# Other declarations
+##############################################################################################
+
+class ASTConcept(ASTBase):
+ def __init__(self, nestedName: ASTNestedName, initializer: ASTInitializer) -> None:
+ self.nestedName = nestedName
+ self.initializer = initializer
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.nestedName
+
+ def get_id(self, version: int, objectType: str | None = None,
+ symbol: Symbol | None = None) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.nestedName)
+ if self.initializer:
+ res += transform(self.initializer)
+ return res
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.nestedName.describe_signature(signode, mode, env, symbol)
+ if self.initializer:
+ self.initializer.describe_signature(signode, mode, env, symbol)
+
+
+class ASTBaseClass(ASTBase):
+ def __init__(self, name: ASTNestedName, visibility: str,
+ virtual: bool, pack: bool) -> None:
+ self.name = name
+ self.visibility = visibility
+ self.virtual = virtual
+ self.pack = pack
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.visibility is not None:
+ res.append(self.visibility)
+ res.append(' ')
+ if self.virtual:
+ res.append('virtual ')
+ res.append(transform(self.name))
+ if self.pack:
+ res.append('...')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.visibility is not None:
+ signode += addnodes.desc_sig_keyword(self.visibility,
+ self.visibility)
+ signode += addnodes.desc_sig_space()
+ if self.virtual:
+ signode += addnodes.desc_sig_keyword('virtual', 'virtual')
+ signode += addnodes.desc_sig_space()
+ self.name.describe_signature(signode, 'markType', env, symbol=symbol)
+ if self.pack:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+
+
+class ASTClass(ASTBase):
+ def __init__(self, name: ASTNestedName, final: bool, bases: list[ASTBaseClass],
+ attrs: ASTAttributeList) -> None:
+ self.name = name
+ self.final = final
+ self.bases = bases
+ self.attrs = attrs
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.attrs))
+ if len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.name))
+ if self.final:
+ res.append(' final')
+ if len(self.bases) > 0:
+ res.append(' : ')
+ first = True
+ for b in self.bases:
+ if not first:
+ res.append(', ')
+ first = False
+ res.append(transform(b))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.attrs.describe_signature(signode)
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.name.describe_signature(signode, mode, env, symbol=symbol)
+ if self.final:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_keyword('final', 'final')
+ if len(self.bases) > 0:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation(':', ':')
+ signode += addnodes.desc_sig_space()
+ for b in self.bases:
+ b.describe_signature(signode, mode, env, symbol=symbol)
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ signode.pop()
+ signode.pop()
+
+
+class ASTUnion(ASTBase):
+ def __init__(self, name: ASTNestedName, attrs: ASTAttributeList) -> None:
+ self.name = name
+ self.attrs = attrs
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.attrs))
+ if len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.name))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.attrs.describe_signature(signode)
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.name.describe_signature(signode, mode, env, symbol=symbol)
+
+
+class ASTEnum(ASTBase):
+ def __init__(self, name: ASTNestedName, scoped: str, underlyingType: ASTType,
+ attrs: ASTAttributeList) -> None:
+ self.name = name
+ self.scoped = scoped
+ self.underlyingType = underlyingType
+ self.attrs = attrs
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.scoped:
+ res.append(self.scoped)
+ res.append(' ')
+ res.append(transform(self.attrs))
+ if len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.name))
+ if self.underlyingType:
+ res.append(' : ')
+ res.append(transform(self.underlyingType))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ # self.scoped has been done by the CPPEnumObject
+ self.attrs.describe_signature(signode)
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.name.describe_signature(signode, mode, env, symbol=symbol)
+ if self.underlyingType:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation(':', ':')
+ signode += addnodes.desc_sig_space()
+ self.underlyingType.describe_signature(signode, 'noneIsName',
+ env, symbol=symbol)
+
+
+class ASTEnumerator(ASTBase):
+ def __init__(self, name: ASTNestedName, init: ASTInitializer | None,
+ attrs: ASTAttributeList) -> None:
+ self.name = name
+ self.init = init
+ self.attrs = attrs
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ if version == 1:
+ raise NoOldIdError
+ return symbol.get_full_nested_name().get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.name))
+ if len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.attrs))
+ if self.init:
+ res.append(transform(self.init))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.name.describe_signature(signode, mode, env, symbol)
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.attrs.describe_signature(signode)
+ if self.init:
+ self.init.describe_signature(signode, 'markType', env, symbol)
+
+
+################################################################################
+# Templates
+################################################################################
+
+# Parameters
+################################################################################
+
+class ASTTemplateParam(ASTBase):
+ def get_identifier(self) -> ASTIdentifier:
+ raise NotImplementedError(repr(self))
+
+ def get_id(self, version: int) -> str:
+ raise NotImplementedError(repr(self))
+
+ def describe_signature(self, parentNode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+ @property
+ def isPack(self) -> bool:
+ raise NotImplementedError(repr(self))
+
+ @property
+ def name(self) -> ASTNestedName:
+ raise NotImplementedError(repr(self))
+
+
+class ASTTemplateKeyParamPackIdDefault(ASTTemplateParam):
+ def __init__(self, key: str, identifier: ASTIdentifier,
+ parameterPack: bool, default: ASTType) -> None:
+ assert key
+ if parameterPack:
+ assert default is None
+ self.key = key
+ self.identifier = identifier
+ self.parameterPack = parameterPack
+ self.default = default
+
+ def get_identifier(self) -> ASTIdentifier:
+ return self.identifier
+
+ def get_id(self, version: int) -> str:
+ assert version >= 2
+ # this is not part of the normal name mangling in C++
+ res = []
+ if self.parameterPack:
+ res.append('Dp')
+ else:
+ res.append('0') # we need to put something
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = [self.key]
+ if self.parameterPack:
+ if self.identifier:
+ res.append(' ')
+ res.append('...')
+ if self.identifier:
+ if not self.parameterPack:
+ res.append(' ')
+ res.append(transform(self.identifier))
+ if self.default:
+ res.append(' = ')
+ res.append(transform(self.default))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword(self.key, self.key)
+ if self.parameterPack:
+ if self.identifier:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ if self.identifier:
+ if not self.parameterPack:
+ signode += addnodes.desc_sig_space()
+ self.identifier.describe_signature(signode, mode, env, '', '', symbol)
+ if self.default:
+ signode += addnodes.desc_sig_space()
+ signode += addnodes.desc_sig_punctuation('=', '=')
+ signode += addnodes.desc_sig_space()
+ self.default.describe_signature(signode, 'markType', env, symbol)
+
+
+class ASTTemplateParamType(ASTTemplateParam):
+ def __init__(self, data: ASTTemplateKeyParamPackIdDefault) -> None:
+ assert data
+ self.data = data
+
+ @property
+ def name(self) -> ASTNestedName:
+ id = self.get_identifier()
+ return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
+
+ @property
+ def isPack(self) -> bool:
+ return self.data.parameterPack
+
+ def get_identifier(self) -> ASTIdentifier:
+ return self.data.get_identifier()
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ # this is not part of the normal name mangling in C++
+ assert version >= 2
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=False)
+ else:
+ return self.data.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.data)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.data.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTemplateParamTemplateType(ASTTemplateParam):
+ def __init__(self, nestedParams: ASTTemplateParams,
+ data: ASTTemplateKeyParamPackIdDefault) -> None:
+ assert nestedParams
+ assert data
+ self.nestedParams = nestedParams
+ self.data = data
+
+ @property
+ def name(self) -> ASTNestedName:
+ id = self.get_identifier()
+ return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
+
+ @property
+ def isPack(self) -> bool:
+ return self.data.parameterPack
+
+ def get_identifier(self) -> ASTIdentifier:
+ return self.data.get_identifier()
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ assert version >= 2
+ # this is not part of the normal name mangling in C++
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=None)
+ else:
+ return self.nestedParams.get_id(version) + self.data.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.nestedParams) + transform(self.data)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol)
+ signode += addnodes.desc_sig_space()
+ self.data.describe_signature(signode, mode, env, symbol)
+
+
+class ASTTemplateParamNonType(ASTTemplateParam):
+ def __init__(self,
+ param: ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit,
+ parameterPack: bool = False) -> None:
+ assert param
+ self.param = param
+ self.parameterPack = parameterPack
+
+ @property
+ def name(self) -> ASTNestedName:
+ id = self.get_identifier()
+ return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
+
+ @property
+ def isPack(self) -> bool:
+ return self.param.isPack or self.parameterPack
+
+ def get_identifier(self) -> ASTIdentifier:
+ name = self.param.name
+ if name:
+ assert len(name.names) == 1
+ assert name.names[0].identOrOp
+ assert not name.names[0].templateArgs
+ res = name.names[0].identOrOp
+ assert isinstance(res, ASTIdentifier)
+ return res
+ else:
+ return None
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ assert version >= 2
+ # this is not part of the normal name mangling in C++
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=None)
+ else:
+ res = '_'
+ if self.parameterPack:
+ res += 'Dp'
+ return res + self.param.get_id(version)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.param)
+ if self.parameterPack:
+ res += '...'
+ return res
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.param.describe_signature(signode, mode, env, symbol)
+ if self.parameterPack:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+
+
+class ASTTemplateParams(ASTBase):
+ def __init__(self, params: list[ASTTemplateParam],
+ requiresClause: ASTRequiresClause | None) -> None:
+ assert params is not None
+ self.params = params
+ self.requiresClause = requiresClause
+
+ def get_id(self, version: int, excludeRequires: bool = False) -> str:
+ assert version >= 2
+ res = []
+ res.append("I")
+ for param in self.params:
+ res.append(param.get_id(version))
+ res.append("E")
+ if not excludeRequires and self.requiresClause:
+ res.append('IQ')
+ res.append(self.requiresClause.expr.get_id(version))
+ res.append('E')
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append("template<")
+ res.append(", ".join(transform(a) for a in self.params))
+ res.append("> ")
+ if self.requiresClause is not None:
+ res.append(transform(self.requiresClause))
+ res.append(" ")
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('template', 'template')
+ signode += addnodes.desc_sig_punctuation('<', '<')
+ first = True
+ for param in self.params:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ first = False
+ param.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation('>', '>')
+ if self.requiresClause is not None:
+ signode += addnodes.desc_sig_space()
+ self.requiresClause.describe_signature(signode, mode, env, symbol)
+
+ def describe_signature_as_introducer(
+ self, parentNode: desc_signature, mode: str, env: BuildEnvironment,
+ symbol: Symbol, lineSpec: bool) -> None:
+ def makeLine(parentNode: desc_signature) -> addnodes.desc_signature_line:
+ signode = addnodes.desc_signature_line()
+ parentNode += signode
+ signode.sphinx_line_type = 'templateParams'
+ return signode
+ lineNode = makeLine(parentNode)
+ lineNode += addnodes.desc_sig_keyword('template', 'template')
+ lineNode += addnodes.desc_sig_punctuation('<', '<')
+ first = True
+ for param in self.params:
+ if not first:
+ lineNode += addnodes.desc_sig_punctuation(',', ',')
+ lineNode += addnodes.desc_sig_space()
+ first = False
+ if lineSpec:
+ lineNode = makeLine(parentNode)
+ param.describe_signature(lineNode, mode, env, symbol)
+ if lineSpec and not first:
+ lineNode = makeLine(parentNode)
+ lineNode += addnodes.desc_sig_punctuation('>', '>')
+ if self.requiresClause:
+ reqNode = addnodes.desc_signature_line()
+ reqNode.sphinx_line_type = 'requiresClause'
+ parentNode += reqNode
+ self.requiresClause.describe_signature(reqNode, 'markType', env, symbol)
+
+
+# Template introducers
+################################################################################
+
+class ASTTemplateIntroductionParameter(ASTBase):
+ def __init__(self, identifier: ASTIdentifier, parameterPack: bool) -> None:
+ self.identifier = identifier
+ self.parameterPack = parameterPack
+
+ @property
+ def name(self) -> ASTNestedName:
+ id = self.get_identifier()
+ return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)
+
+ @property
+ def isPack(self) -> bool:
+ return self.parameterPack
+
+ def get_identifier(self) -> ASTIdentifier:
+ return self.identifier
+
+ def get_id(
+ self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
+ ) -> str:
+ assert version >= 2
+ # this is not part of the normal name mangling in C++
+ if symbol:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=None)
+ else:
+ if self.parameterPack:
+ return 'Dp'
+ else:
+ return '0' # we need to put something
+
+ def get_id_as_arg(self, version: int) -> str:
+ assert version >= 2
+ # used for the implicit requires clause
+ res = self.identifier.get_id(version)
+ if self.parameterPack:
+ return 'sp' + res
+ else:
+ return res
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.parameterPack:
+ res.append('...')
+ res.append(transform(self.identifier))
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ if self.parameterPack:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ self.identifier.describe_signature(signode, mode, env, '', '', symbol)
+
+
+class ASTTemplateIntroduction(ASTBase):
+ def __init__(self, concept: ASTNestedName,
+ params: list[ASTTemplateIntroductionParameter]) -> None:
+ assert len(params) > 0
+ self.concept = concept
+ self.params = params
+
+ def get_id(self, version: int) -> str:
+ assert version >= 2
+ # first do the same as a normal template parameter list
+ res = []
+ res.append("I")
+ for param in self.params:
+ res.append(param.get_id(version))
+ res.append("E")
+ # let's use X expr E, which is otherwise for constant template args
+ res.append("X")
+ res.append(self.concept.get_id(version))
+ res.append("I")
+ for param in self.params:
+ res.append(param.get_id_as_arg(version))
+ res.append("E")
+ res.append("E")
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ res.append(transform(self.concept))
+ res.append('{')
+ res.append(', '.join(transform(param) for param in self.params))
+ res.append('} ')
+ return ''.join(res)
+
+ def describe_signature_as_introducer(
+ self, parentNode: desc_signature, mode: str,
+ env: BuildEnvironment, symbol: Symbol, lineSpec: bool) -> None:
+ # Note: 'lineSpec' has no effect on template introductions.
+ signode = addnodes.desc_signature_line()
+ parentNode += signode
+ signode.sphinx_line_type = 'templateIntroduction'
+ self.concept.describe_signature(signode, 'markType', env, symbol)
+ signode += addnodes.desc_sig_punctuation('{', '{')
+ first = True
+ for param in self.params:
+ if not first:
+ signode += addnodes.desc_sig_punctuation(',', ',')
+ signode += addnodes.desc_sig_space()
+ first = False
+ param.describe_signature(signode, mode, env, symbol)
+ signode += addnodes.desc_sig_punctuation('}', '}')
+
+
+################################################################################
+
+class ASTTemplateDeclarationPrefix(ASTBase):
+ def __init__(self,
+ templates: list[ASTTemplateParams | ASTTemplateIntroduction]) -> None:
+ # templates is None means it's an explicit instantiation of a variable
+ self.templates = templates
+
+ def get_requires_clause_in_last(self) -> ASTRequiresClause | None:
+ if self.templates is None:
+ return None
+ lastList = self.templates[-1]
+ if not isinstance(lastList, ASTTemplateParams):
+ return None
+ return lastList.requiresClause # which may be None
+
+ def get_id_except_requires_clause_in_last(self, version: int) -> str:
+ assert version >= 2
+ # This is not part of the Itanium ABI mangling system.
+ res = []
+ lastIndex = len(self.templates) - 1
+ for i, t in enumerate(self.templates):
+ if isinstance(t, ASTTemplateParams):
+ res.append(t.get_id(version, excludeRequires=(i == lastIndex)))
+ else:
+ res.append(t.get_id(version))
+ return ''.join(res)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ for t in self.templates:
+ res.append(transform(t))
+ return ''.join(res)
+
+ def describe_signature(self, signode: desc_signature, mode: str,
+ env: BuildEnvironment, symbol: Symbol, lineSpec: bool) -> None:
+ verify_description_mode(mode)
+ for t in self.templates:
+ t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)
+
+
+class ASTRequiresClause(ASTBase):
+ def __init__(self, expr: ASTExpression) -> None:
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return 'requires ' + transform(self.expr)
+
+ def describe_signature(self, signode: nodes.TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_keyword('requires', 'requires')
+ signode += addnodes.desc_sig_space()
+ self.expr.describe_signature(signode, mode, env, symbol)
+
+
+################################################################################
+################################################################################
+
+class ASTDeclaration(ASTBase):
+ def __init__(self, objectType: str, directiveType: str | None = None,
+ visibility: str | None = None,
+ templatePrefix: ASTTemplateDeclarationPrefix | None = None,
+ declaration: Any = None,
+ trailingRequiresClause: ASTRequiresClause | None = None,
+ semicolon: bool = False) -> None:
+ self.objectType = objectType
+ self.directiveType = directiveType
+ self.visibility = visibility
+ self.templatePrefix = templatePrefix
+ self.declaration = declaration
+ self.trailingRequiresClause = trailingRequiresClause
+ self.semicolon = semicolon
+
+ self.symbol: Symbol = None
+ # set by CPPObject._add_enumerator_to_parent
+ self.enumeratorScopedSymbol: Symbol = None
+
+ def clone(self) -> ASTDeclaration:
+ templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
+ trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
+ if self.trailingRequiresClause else None
+ return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
+ templatePrefixClone,
+ self.declaration.clone(), trailingRequiresClasueClone,
+ self.semicolon)
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.declaration.name
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ if self.objectType != 'function':
+ return None
+ return self.declaration.function_params
+
+ def get_id(self, version: int, prefixed: bool = True) -> str:
+ if version == 1:
+ if self.templatePrefix or self.trailingRequiresClause:
+ raise NoOldIdError
+ if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
+ return self.enumeratorScopedSymbol.declaration.get_id(version)
+ return self.declaration.get_id(version, self.objectType, self.symbol)
+ # version >= 2
+ if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
+ return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
+ if prefixed:
+ res = [_id_prefix[version]]
+ else:
+ res = []
+ # (See also https://github.com/sphinx-doc/sphinx/pull/10286#issuecomment-1168102147)
+ # The first implementation of requires clauses only supported a single clause after the
+ # template prefix, and no trailing clause. It put the ID after the template parameter
+ # list, i.e.,
+ # "I" + template_parameter_list_id + "E" + "IQ" + requires_clause_id + "E"
+ # but the second implementation associates the requires clause with each list, i.e.,
+ # "I" + template_parameter_list_id + "IQ" + requires_clause_id + "E" + "E"
+ # To avoid making a new ID version, we make an exception for the last requires clause
+ # in the template prefix, and still put it in the end.
+ # As we now support trailing requires clauses we add that as if it was a conjunction.
+ if self.templatePrefix is not None:
+ res.append(self.templatePrefix.get_id_except_requires_clause_in_last(version))
+ requiresClauseInLast = self.templatePrefix.get_requires_clause_in_last()
+ else:
+ requiresClauseInLast = None
+
+ if requiresClauseInLast or self.trailingRequiresClause:
+ if version < 4:
+ raise NoOldIdError
+ res.append('IQ')
+ if requiresClauseInLast and self.trailingRequiresClause:
+ # make a conjunction of them
+ res.append('aa')
+ if requiresClauseInLast:
+ res.append(requiresClauseInLast.expr.get_id(version))
+ if self.trailingRequiresClause:
+ res.append(self.trailingRequiresClause.expr.get_id(version))
+ res.append('E')
+ res.append(self.declaration.get_id(version, self.objectType, self.symbol))
+ return ''.join(res)
+
+ def get_newest_id(self) -> str:
+ return self.get_id(_max_id, True)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.visibility and self.visibility != "public":
+ res.append(self.visibility)
+ res.append(' ')
+ if self.templatePrefix:
+ res.append(transform(self.templatePrefix))
+ res.append(transform(self.declaration))
+ if self.trailingRequiresClause:
+ res.append(' ')
+ res.append(transform(self.trailingRequiresClause))
+ if self.semicolon:
+ res.append(';')
+ return ''.join(res)
+
+ def describe_signature(self, signode: desc_signature, mode: str,
+ env: BuildEnvironment, options: dict) -> None:
+ verify_description_mode(mode)
+ assert self.symbol
+ # The caller of the domain added a desc_signature node.
+ # Always enable multiline:
+ signode['is_multiline'] = True
+ # Put each line in a desc_signature_line node.
+ mainDeclNode = addnodes.desc_signature_line()
+ mainDeclNode.sphinx_line_type = 'declarator'
+ mainDeclNode['add_permalink'] = not self.symbol.isRedeclaration
+
+ if self.templatePrefix:
+ self.templatePrefix.describe_signature(signode, mode, env,
+ symbol=self.symbol,
+ lineSpec=options.get('tparam-line-spec'))
+ signode += mainDeclNode
+ if self.visibility and self.visibility != "public":
+ mainDeclNode += addnodes.desc_sig_keyword(self.visibility, self.visibility)
+ mainDeclNode += addnodes.desc_sig_space()
+ if self.objectType == 'type':
+ prefix = self.declaration.get_type_declaration_prefix()
+ mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix)
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.objectType == 'concept':
+ mainDeclNode += addnodes.desc_sig_keyword('concept', 'concept')
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.objectType in {'member', 'function'}:
+ pass
+ elif self.objectType == 'class':
+ assert self.directiveType in ('class', 'struct')
+ mainDeclNode += addnodes.desc_sig_keyword(self.directiveType, self.directiveType)
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.objectType == 'union':
+ mainDeclNode += addnodes.desc_sig_keyword('union', 'union')
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.objectType == 'enum':
+ mainDeclNode += addnodes.desc_sig_keyword('enum', 'enum')
+ mainDeclNode += addnodes.desc_sig_space()
+ if self.directiveType == 'enum-class':
+ mainDeclNode += addnodes.desc_sig_keyword('class', 'class')
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.directiveType == 'enum-struct':
+ mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct')
+ mainDeclNode += addnodes.desc_sig_space()
+ else:
+ assert self.directiveType == 'enum', self.directiveType
+ elif self.objectType == 'enumerator':
+ mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator')
+ mainDeclNode += addnodes.desc_sig_space()
+ else:
+ raise AssertionError(self.objectType)
+ self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
+ lastDeclNode = mainDeclNode
+ if self.trailingRequiresClause:
+ trailingReqNode = addnodes.desc_signature_line()
+ trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
+ signode.append(trailingReqNode)
+ lastDeclNode = trailingReqNode
+ self.trailingRequiresClause.describe_signature(
+ trailingReqNode, 'markType', env, self.symbol)
+ if self.semicolon:
+ lastDeclNode += addnodes.desc_sig_punctuation(';', ';')
+
+
+class ASTNamespace(ASTBase):
+ def __init__(self, nestedName: ASTNestedName,
+ templatePrefix: ASTTemplateDeclarationPrefix) -> None:
+ self.nestedName = nestedName
+ self.templatePrefix = templatePrefix
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.templatePrefix:
+ res.append(transform(self.templatePrefix))
+ res.append(transform(self.nestedName))
+ return ''.join(res)
+
+
+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):
+ raise AssertionError # shouldn't happen
+
+ def __deepcopy__(self, memo):
+ 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 _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) -> Generator[Symbol, None, None]:
+ 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: Any, templateArgs: ASTTemplateArgs,
+ templateShorthand: bool, matchSelf: bool,
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ ) -> Symbol:
+ 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: Any, 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<typename T> int A<T>::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() -> Generator[Symbol, None, None]:
+ 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, docname: str, line: int) -> 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, otherChild):
+ # 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:
+ 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], 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:
+ # 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:
+ res = [self.to_string(indent)]
+ for c in self._children:
+ res.append(c.dump(indent + 1))
+ return ''.join(res)
+
+
+class DefinitionParser(BaseParser):
+ @property
+ def language(self) -> str:
+ return 'C++'
+
+ @property
+ def id_attributes(self):
+ return self.config.cpp_id_attributes
+
+ @property
+ def paren_attributes(self):
+ return self.config.cpp_paren_attributes
+
+ def _parse_string(self) -> str:
+ if self.current_char != '"':
+ return None
+ startPos = self.pos
+ self.pos += 1
+ escape = False
+ while True:
+ if self.eof:
+ self.fail("Unexpected end during inside string.")
+ elif self.current_char == '"' and not escape:
+ self.pos += 1
+ break
+ elif self.current_char == '\\':
+ escape = True
+ else:
+ escape = False
+ self.pos += 1
+ return self.definition[startPos:self.pos]
+
+ def _parse_literal(self) -> ASTLiteral:
+ # -> integer-literal
+ # | character-literal
+ # | floating-literal
+ # | string-literal
+ # | boolean-literal -> "false" | "true"
+ # | pointer-literal -> "nullptr"
+ # | user-defined-literal
+
+ def _udl(literal: ASTLiteral) -> ASTLiteral:
+ if not self.match(udl_identifier_re):
+ return literal
+ # hmm, should we care if it's a keyword?
+ # it looks like GCC does not disallow keywords
+ ident = ASTIdentifier(self.matched_text)
+ return ASTUserDefinedLiteral(literal, ident)
+
+ self.skip_ws()
+ if self.skip_word('nullptr'):
+ return ASTPointerLiteral()
+ if self.skip_word('true'):
+ return ASTBooleanLiteral(True)
+ if self.skip_word('false'):
+ return ASTBooleanLiteral(False)
+ pos = self.pos
+ if self.match(float_literal_re):
+ hasSuffix = self.match(float_literal_suffix_re)
+ floatLit = ASTNumberLiteral(self.definition[pos:self.pos])
+ if hasSuffix:
+ return floatLit
+ else:
+ return _udl(floatLit)
+ for regex in [binary_literal_re, hex_literal_re,
+ integer_literal_re, octal_literal_re]:
+ if self.match(regex):
+ hasSuffix = self.match(integers_literal_suffix_re)
+ intLit = ASTNumberLiteral(self.definition[pos:self.pos])
+ if hasSuffix:
+ return intLit
+ else:
+ return _udl(intLit)
+
+ string = self._parse_string()
+ if string is not None:
+ return _udl(ASTStringLiteral(string))
+
+ # character-literal
+ if self.match(char_literal_re):
+ prefix = self.last_match.group(1) # may be None when no prefix
+ data = self.last_match.group(2)
+ try:
+ charLit = ASTCharLiteral(prefix, data)
+ except UnicodeDecodeError as e:
+ self.fail("Can not handle character literal. Internal error was: %s" % e)
+ except UnsupportedMultiCharacterCharLiteral:
+ self.fail("Can not handle character literal"
+ " resulting in multiple decoded characters.")
+ return _udl(charLit)
+ return None
+
+ def _parse_fold_or_paren_expression(self) -> ASTExpression:
+ # "(" expression ")"
+ # fold-expression
+ # -> ( cast-expression fold-operator ... )
+ # | ( ... fold-operator cast-expression )
+ # | ( cast-expression fold-operator ... fold-operator cast-expression
+ if self.current_char != '(':
+ return None
+ self.pos += 1
+ self.skip_ws()
+ if self.skip_string_and_ws("..."):
+ # ( ... fold-operator cast-expression )
+ if not self.match(_fold_operator_re):
+ self.fail("Expected fold operator after '...' in fold expression.")
+ op = self.matched_text
+ rightExpr = self._parse_cast_expression()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in end of fold expression.")
+ return ASTFoldExpr(None, op, rightExpr)
+ # try first parsing a unary right fold, or a binary fold
+ pos = self.pos
+ try:
+ self.skip_ws()
+ leftExpr = self._parse_cast_expression()
+ self.skip_ws()
+ if not self.match(_fold_operator_re):
+ self.fail("Expected fold operator after left expression in fold expression.")
+ op = self.matched_text
+ self.skip_ws()
+ if not self.skip_string_and_ws('...'):
+ self.fail("Expected '...' after fold operator in fold expression.")
+ except DefinitionError as eFold:
+ self.pos = pos
+ # fall back to a paren expression
+ try:
+ res = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in end of parenthesized expression.")
+ except DefinitionError as eExpr:
+ raise self._make_multi_error([
+ (eFold, "If fold expression"),
+ (eExpr, "If parenthesized expression"),
+ ], "Error in fold expression or parenthesized expression.") from eExpr
+ return ASTParenExpr(res)
+ # now it definitely is a fold expression
+ if self.skip_string(')'):
+ return ASTFoldExpr(leftExpr, op, None)
+ if not self.match(_fold_operator_re):
+ self.fail("Expected fold operator or ')' after '...' in fold expression.")
+ if op != self.matched_text:
+ self.fail("Operators are different in binary fold: '%s' and '%s'."
+ % (op, self.matched_text))
+ rightExpr = self._parse_cast_expression()
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' to end binary fold expression.")
+ return ASTFoldExpr(leftExpr, op, rightExpr)
+
+ def _parse_primary_expression(self) -> ASTExpression:
+ # literal
+ # "this"
+ # lambda-expression
+ # "(" expression ")"
+ # fold-expression
+ # id-expression -> we parse this with _parse_nested_name
+ self.skip_ws()
+ res: ASTExpression = self._parse_literal()
+ if res is not None:
+ return res
+ self.skip_ws()
+ if self.skip_word("this"):
+ return ASTThisLiteral()
+ # TODO: try lambda expression
+ res = self._parse_fold_or_paren_expression()
+ if res is not None:
+ return res
+ nn = self._parse_nested_name()
+ if nn is not None:
+ return ASTIdExpression(nn)
+ return None
+
+ def _parse_initializer_list(self, name: str, open: str, close: str,
+ ) -> tuple[list[ASTExpression | ASTBracedInitList],
+ bool]:
+ # Parse open and close with the actual initializer-list in between
+ # -> initializer-clause '...'[opt]
+ # | initializer-list ',' initializer-clause '...'[opt]
+ self.skip_ws()
+ if not self.skip_string_and_ws(open):
+ return None, None
+ if self.skip_string(close):
+ return [], False
+
+ exprs: list[ASTExpression | ASTBracedInitList] = []
+ trailingComma = False
+ while True:
+ self.skip_ws()
+ expr = self._parse_initializer_clause()
+ self.skip_ws()
+ if self.skip_string('...'):
+ exprs.append(ASTPackExpansionExpr(expr))
+ else:
+ exprs.append(expr)
+ self.skip_ws()
+ if self.skip_string(close):
+ break
+ if not self.skip_string_and_ws(','):
+ self.fail(f"Error in {name}, expected ',' or '{close}'.")
+ if self.current_char == close and close == '}':
+ self.pos += 1
+ trailingComma = True
+ break
+ return exprs, trailingComma
+
+ def _parse_paren_expression_list(self) -> ASTParenExprList:
+ # -> '(' expression-list ')'
+ # though, we relax it to also allow empty parens
+ # as it's needed in some cases
+ #
+ # expression-list
+ # -> initializer-list
+ exprs, trailingComma = self._parse_initializer_list("parenthesized expression-list",
+ '(', ')')
+ if exprs is None:
+ return None
+ return ASTParenExprList(exprs)
+
+ def _parse_initializer_clause(self) -> ASTExpression | ASTBracedInitList:
+ bracedInitList = self._parse_braced_init_list()
+ if bracedInitList is not None:
+ return bracedInitList
+ return self._parse_assignment_expression(inTemplate=False)
+
+ def _parse_braced_init_list(self) -> ASTBracedInitList:
+ # -> '{' initializer-list ','[opt] '}'
+ # | '{' '}'
+ exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
+ if exprs is None:
+ return None
+ return ASTBracedInitList(exprs, trailingComma)
+
+ def _parse_expression_list_or_braced_init_list(
+ self,
+ ) -> ASTParenExprList | ASTBracedInitList:
+ paren = self._parse_paren_expression_list()
+ if paren is not None:
+ return paren
+ return self._parse_braced_init_list()
+
+ def _parse_postfix_expression(self) -> ASTPostfixExpr:
+ # -> primary
+ # | postfix "[" expression "]"
+ # | postfix "[" braced-init-list [opt] "]"
+ # | postfix "(" expression-list [opt] ")"
+ # | postfix "." "template" [opt] id-expression
+ # | postfix "->" "template" [opt] id-expression
+ # | postfix "." pseudo-destructor-name
+ # | postfix "->" pseudo-destructor-name
+ # | postfix "++"
+ # | postfix "--"
+ # | simple-type-specifier "(" expression-list [opt] ")"
+ # | simple-type-specifier braced-init-list
+ # | typename-specifier "(" expression-list [opt] ")"
+ # | typename-specifier braced-init-list
+ # | "dynamic_cast" "<" type-id ">" "(" expression ")"
+ # | "static_cast" "<" type-id ">" "(" expression ")"
+ # | "reinterpret_cast" "<" type-id ">" "(" expression ")"
+ # | "const_cast" "<" type-id ">" "(" expression ")"
+ # | "typeid" "(" expression ")"
+ # | "typeid" "(" type-id ")"
+
+ prefixType = None
+ prefix: Any = None
+ self.skip_ws()
+
+ cast = None
+ for c in _id_explicit_cast:
+ if self.skip_word_and_ws(c):
+ cast = c
+ break
+ if cast is not None:
+ prefixType = "cast"
+ if not self.skip_string("<"):
+ self.fail("Expected '<' after '%s'." % cast)
+ typ = self._parse_type(False)
+ self.skip_ws()
+ if not self.skip_string_and_ws(">"):
+ self.fail("Expected '>' after type in '%s'." % cast)
+ if not self.skip_string("("):
+ self.fail("Expected '(' in '%s'." % cast)
+
+ def parser() -> ASTExpression:
+ return self._parse_expression()
+ expr = self._parse_expression_fallback([')'], parser)
+ self.skip_ws()
+ if not self.skip_string(")"):
+ self.fail("Expected ')' to end '%s'." % cast)
+ prefix = ASTExplicitCast(cast, typ, expr)
+ elif self.skip_word_and_ws("typeid"):
+ prefixType = "typeid"
+ if not self.skip_string_and_ws('('):
+ self.fail("Expected '(' after 'typeid'.")
+ pos = self.pos
+ try:
+ typ = self._parse_type(False)
+ prefix = ASTTypeId(typ, isType=True)
+ if not self.skip_string(')'):
+ self.fail("Expected ')' to end 'typeid' of type.")
+ except DefinitionError as eType:
+ self.pos = pos
+ try:
+
+ def parser() -> ASTExpression:
+ return self._parse_expression()
+ expr = self._parse_expression_fallback([')'], parser)
+ prefix = ASTTypeId(expr, isType=False)
+ if not self.skip_string(')'):
+ self.fail("Expected ')' to end 'typeid' of expression.")
+ except DefinitionError as eExpr:
+ self.pos = pos
+ header = "Error in 'typeid(...)'."
+ header += " Expected type or expression."
+ errors = []
+ errors.append((eType, "If type"))
+ errors.append((eExpr, "If expression"))
+ raise self._make_multi_error(errors, header) from eExpr
+ else: # a primary expression or a type
+ pos = self.pos
+ try:
+ prefix = self._parse_primary_expression()
+ prefixType = 'expr'
+ except DefinitionError as eOuter:
+ self.pos = pos
+ try:
+ # we are potentially casting, so save parens for us
+ # TODO: hmm, would we need to try both with operatorCast and with None?
+ prefix = self._parse_type(False, 'operatorCast')
+ prefixType = 'typeOperatorCast'
+ # | simple-type-specifier "(" expression-list [opt] ")"
+ # | simple-type-specifier braced-init-list
+ # | typename-specifier "(" expression-list [opt] ")"
+ # | typename-specifier braced-init-list
+ self.skip_ws()
+ if self.current_char != '(' and self.current_char != '{':
+ self.fail("Expecting '(' or '{' after type in cast expression.")
+ except DefinitionError as eInner:
+ self.pos = pos
+ header = "Error in postfix expression,"
+ header += " expected primary expression or type."
+ errors = []
+ errors.append((eOuter, "If primary expression"))
+ errors.append((eInner, "If type"))
+ raise self._make_multi_error(errors, header) from eInner
+
+ # and now parse postfixes
+ postFixes: list[ASTPostfixOp] = []
+ while True:
+ self.skip_ws()
+ if prefixType in ('expr', 'cast', 'typeid'):
+ if self.skip_string_and_ws('['):
+ expr = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(']'):
+ self.fail("Expected ']' in end of postfix expression.")
+ postFixes.append(ASTPostfixArray(expr))
+ continue
+ if self.skip_string('.'):
+ if self.skip_string('*'):
+ # don't steal the dot
+ self.pos -= 2
+ elif self.skip_string('..'):
+ # don't steal the dot
+ self.pos -= 3
+ else:
+ name = self._parse_nested_name()
+ postFixes.append(ASTPostfixMember(name))
+ continue
+ if self.skip_string('->'):
+ if self.skip_string('*'):
+ # don't steal the arrow
+ self.pos -= 3
+ else:
+ name = self._parse_nested_name()
+ postFixes.append(ASTPostfixMemberOfPointer(name))
+ continue
+ if self.skip_string('++'):
+ postFixes.append(ASTPostfixInc())
+ continue
+ if self.skip_string('--'):
+ postFixes.append(ASTPostfixDec())
+ continue
+ lst = self._parse_expression_list_or_braced_init_list()
+ if lst is not None:
+ postFixes.append(ASTPostfixCallExpr(lst))
+ continue
+ break
+ return ASTPostfixExpr(prefix, postFixes)
+
+ def _parse_unary_expression(self) -> ASTExpression:
+ # -> postfix
+ # | "++" cast
+ # | "--" cast
+ # | unary-operator cast -> (* | & | + | - | ! | ~) cast
+ # The rest:
+ # | "sizeof" unary
+ # | "sizeof" "(" type-id ")"
+ # | "sizeof" "..." "(" identifier ")"
+ # | "alignof" "(" type-id ")"
+ # | noexcept-expression -> noexcept "(" expression ")"
+ # | new-expression
+ # | delete-expression
+ self.skip_ws()
+ for op in _expression_unary_ops:
+ # TODO: hmm, should we be able to backtrack here?
+ if op[0] in 'cn':
+ res = self.skip_word(op)
+ else:
+ res = self.skip_string(op)
+ if res:
+ expr = self._parse_cast_expression()
+ return ASTUnaryOpExpr(op, expr)
+ if self.skip_word_and_ws('sizeof'):
+ if self.skip_string_and_ws('...'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expecting '(' after 'sizeof...'.")
+ if not self.match(identifier_re):
+ self.fail("Expecting identifier for 'sizeof...'.")
+ ident = ASTIdentifier(self.matched_text)
+ self.skip_ws()
+ if not self.skip_string(")"):
+ self.fail("Expecting ')' to end 'sizeof...'.")
+ return ASTSizeofParamPack(ident)
+ if self.skip_string_and_ws('('):
+ typ = self._parse_type(named=False)
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expecting ')' to end 'sizeof'.")
+ return ASTSizeofType(typ)
+ expr = self._parse_unary_expression()
+ return ASTSizeofExpr(expr)
+ if self.skip_word_and_ws('alignof'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expecting '(' after 'alignof'.")
+ typ = self._parse_type(named=False)
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expecting ')' to end 'alignof'.")
+ return ASTAlignofExpr(typ)
+ if self.skip_word_and_ws('noexcept'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expecting '(' after 'noexcept'.")
+ expr = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expecting ')' to end 'noexcept'.")
+ return ASTNoexceptExpr(expr)
+ # new-expression
+ pos = self.pos
+ rooted = self.skip_string('::')
+ self.skip_ws()
+ if not self.skip_word_and_ws('new'):
+ self.pos = pos
+ else:
+ # new-placement[opt] new-type-id new-initializer[opt]
+ # new-placement[opt] ( type-id ) new-initializer[opt]
+ isNewTypeId = True
+ if self.skip_string_and_ws('('):
+ # either this is a new-placement or it's the second production
+ # without placement, and it's actually the ( type-id ) part
+ self.fail("Sorry, neither new-placement nor parenthesised type-id "
+ "in new-epression is supported yet.")
+ # set isNewTypeId = False if it's (type-id)
+ if isNewTypeId:
+ declSpecs = self._parse_decl_specs(outer=None)
+ decl = self._parse_declarator(named=False, paramMode="new")
+ else:
+ self.fail("Sorry, parenthesised type-id in new expression not yet supported.")
+ lst = self._parse_expression_list_or_braced_init_list()
+ return ASTNewExpr(rooted, isNewTypeId, ASTType(declSpecs, decl), lst)
+ # delete-expression
+ pos = self.pos
+ rooted = self.skip_string('::')
+ self.skip_ws()
+ if not self.skip_word_and_ws('delete'):
+ self.pos = pos
+ else:
+ array = self.skip_string_and_ws('[')
+ if array and not self.skip_string_and_ws(']'):
+ self.fail("Expected ']' in array delete-expression.")
+ expr = self._parse_cast_expression()
+ return ASTDeleteExpr(rooted, array, expr)
+ return self._parse_postfix_expression()
+
+ def _parse_cast_expression(self) -> ASTExpression:
+ # -> unary | "(" type-id ")" cast
+ pos = self.pos
+ self.skip_ws()
+ if self.skip_string('('):
+ try:
+ typ = self._parse_type(False)
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in cast expression.")
+ expr = self._parse_cast_expression()
+ return ASTCastExpr(typ, expr)
+ except DefinitionError as exCast:
+ self.pos = pos
+ try:
+ return self._parse_unary_expression()
+ except DefinitionError as exUnary:
+ errs = []
+ errs.append((exCast, "If type cast expression"))
+ errs.append((exUnary, "If unary expression"))
+ raise self._make_multi_error(errs,
+ "Error in cast expression.") from exUnary
+ else:
+ return self._parse_unary_expression()
+
+ def _parse_logical_or_expression(self, inTemplate: bool) -> ASTExpression:
+ # logical-or = logical-and ||
+ # logical-and = inclusive-or &&
+ # inclusive-or = exclusive-or |
+ # exclusive-or = and ^
+ # and = equality &
+ # equality = relational ==, !=
+ # relational = shift <, >, <=, >=, <=>
+ # shift = additive <<, >>
+ # additive = multiplicative +, -
+ # multiplicative = pm *, /, %
+ # pm = cast .*, ->*
+ def _parse_bin_op_expr(self: DefinitionParser,
+ opId: int, inTemplate: bool) -> ASTExpression:
+ if opId + 1 == len(_expression_bin_ops):
+ def parser(inTemplate: bool) -> ASTExpression:
+ return self._parse_cast_expression()
+ else:
+ def parser(inTemplate: bool) -> ASTExpression:
+ return _parse_bin_op_expr(self, opId + 1, inTemplate=inTemplate)
+ exprs = []
+ ops = []
+ exprs.append(parser(inTemplate=inTemplate))
+ while True:
+ self.skip_ws()
+ if inTemplate and self.current_char == '>':
+ break
+ pos = self.pos
+ oneMore = False
+ for op in _expression_bin_ops[opId]:
+ if op[0] in 'abcnox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
+ if op == '&' and self.current_char == '&':
+ # don't split the && 'token'
+ self.pos -= 1
+ # and btw. && has lower precedence, so we are done
+ break
+ try:
+ expr = parser(inTemplate=inTemplate)
+ exprs.append(expr)
+ ops.append(op)
+ oneMore = True
+ break
+ except DefinitionError:
+ self.pos = pos
+ if not oneMore:
+ break
+ return ASTBinOpExpr(exprs, ops)
+ return _parse_bin_op_expr(self, 0, inTemplate=inTemplate)
+
+ def _parse_conditional_expression_tail(self, orExprHead: ASTExpression,
+ inTemplate: bool) -> ASTConditionalExpr | None:
+ # Consumes the orExprHead on success.
+
+ # -> "?" expression ":" assignment-expression
+ self.skip_ws()
+ if not self.skip_string("?"):
+ return None
+ thenExpr = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(":"):
+ self.fail('Expected ":" after then-expression in conditional expression.')
+ elseExpr = self._parse_assignment_expression(inTemplate)
+ return ASTConditionalExpr(orExprHead, thenExpr, elseExpr)
+
+ def _parse_assignment_expression(self, inTemplate: bool) -> ASTExpression:
+ # -> conditional-expression
+ # | logical-or-expression assignment-operator initializer-clause
+ # | yield-expression -> "co_yield" assignment-expression
+ # | "co_yield" braced-init-list
+ # | throw-expression -> "throw" assignment-expression[opt]
+ # TODO: yield-expression
+ # TODO: throw-expression
+
+ # Now we have (after expanding conditional-expression:
+ # logical-or-expression
+ # | logical-or-expression "?" expression ":" assignment-expression
+ # | logical-or-expression assignment-operator initializer-clause
+ leftExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
+ # the ternary operator
+ condExpr = self._parse_conditional_expression_tail(leftExpr, inTemplate)
+ if condExpr is not None:
+ return condExpr
+ # and actual assignment
+ for op in _expression_assignment_ops:
+ if op[0] in 'anox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
+ rightExpr = self._parse_initializer_clause()
+ return ASTAssignmentExpr(leftExpr, op, rightExpr)
+ # just a logical-or-expression
+ return leftExpr
+
+ def _parse_constant_expression(self, inTemplate: bool) -> ASTExpression:
+ # -> conditional-expression ->
+ # logical-or-expression
+ # | logical-or-expression "?" expression ":" assignment-expression
+ orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
+ condExpr = self._parse_conditional_expression_tail(orExpr, inTemplate)
+ if condExpr is not None:
+ return condExpr
+ return orExpr
+
+ def _parse_expression(self) -> ASTExpression:
+ # -> assignment-expression
+ # | expression "," assignment-expression
+ exprs = [self._parse_assignment_expression(inTemplate=False)]
+ while True:
+ self.skip_ws()
+ if not self.skip_string(','):
+ break
+ exprs.append(self._parse_assignment_expression(inTemplate=False))
+ if len(exprs) == 1:
+ return exprs[0]
+ else:
+ return ASTCommaExpr(exprs)
+
+ def _parse_expression_fallback(self, end: list[str],
+ parser: Callable[[], ASTExpression],
+ allow: bool = True) -> ASTExpression:
+ # Stupidly "parse" an expression.
+ # 'end' should be a list of characters which ends the expression.
+
+ # first try to use the provided parser
+ prevPos = self.pos
+ try:
+ return parser()
+ except DefinitionError as e:
+ # some places (e.g., template parameters) we really don't want to use fallback,
+ # and for testing we may want to globally disable it
+ if not allow or not self.allowFallbackExpressionParsing:
+ raise
+ self.warn("Parsing of expression failed. Using fallback parser."
+ " Error was:\n%s" % e)
+ self.pos = prevPos
+ # and then the fallback scanning
+ assert end is not None
+ self.skip_ws()
+ startPos = self.pos
+ if self.match(_string_re):
+ value = self.matched_text
+ else:
+ # TODO: add handling of more bracket-like things, and quote handling
+ brackets = {'(': ')', '{': '}', '[': ']', '<': '>'}
+ symbols: list[str] = []
+ while not self.eof:
+ if (len(symbols) == 0 and self.current_char in end):
+ break
+ if self.current_char in brackets:
+ symbols.append(brackets[self.current_char])
+ elif len(symbols) > 0 and self.current_char == symbols[-1]:
+ symbols.pop()
+ self.pos += 1
+ if len(end) > 0 and self.eof:
+ self.fail("Could not find end of expression starting at %d."
+ % startPos)
+ value = self.definition[startPos:self.pos].strip()
+ return ASTFallbackExpr(value.strip())
+
+ # ==========================================================================
+
+ def _parse_operator(self) -> ASTOperator:
+ self.skip_ws()
+ # adapted from the old code
+ # yay, a regular operator definition
+ if self.match(_operator_re):
+ return ASTOperatorBuildIn(self.matched_text)
+
+ # new/delete operator?
+ for op in 'new', 'delete':
+ if not self.skip_word(op):
+ continue
+ self.skip_ws()
+ if self.skip_string('['):
+ self.skip_ws()
+ if not self.skip_string(']'):
+ self.fail('Expected "]" after "operator ' + op + '["')
+ op += '[]'
+ return ASTOperatorBuildIn(op)
+
+ # user-defined literal?
+ if self.skip_string('""'):
+ self.skip_ws()
+ if not self.match(identifier_re):
+ self.fail("Expected user-defined literal suffix.")
+ identifier = ASTIdentifier(self.matched_text)
+ return ASTOperatorLiteral(identifier)
+
+ # oh well, looks like a cast operator definition.
+ # In that case, eat another type.
+ type = self._parse_type(named=False, outer="operatorCast")
+ return ASTOperatorType(type)
+
+ def _parse_template_argument_list(self) -> ASTTemplateArgs:
+ # template-argument-list: (but we include the < and > here
+ # template-argument ...[opt]
+ # template-argument-list, template-argument ...[opt]
+ # template-argument:
+ # constant-expression
+ # type-id
+ # id-expression
+ self.skip_ws()
+ if not self.skip_string_and_ws('<'):
+ return None
+ if self.skip_string('>'):
+ return ASTTemplateArgs([], False)
+ prevErrors = []
+ templateArgs: list[ASTType | ASTTemplateArgConstant] = []
+ packExpansion = False
+ while 1:
+ pos = self.pos
+ parsedComma = False
+ parsedEnd = False
+ try:
+ type = self._parse_type(named=False)
+ self.skip_ws()
+ if self.skip_string_and_ws('...'):
+ packExpansion = True
+ parsedEnd = True
+ if not self.skip_string('>'):
+ self.fail('Expected ">" after "..." in template argument list.')
+ elif self.skip_string('>'):
+ parsedEnd = True
+ elif self.skip_string(','):
+ parsedComma = True
+ else:
+ self.fail('Expected "...>", ">" or "," in template argument list.')
+ templateArgs.append(type)
+ except DefinitionError as e:
+ prevErrors.append((e, "If type argument"))
+ self.pos = pos
+ try:
+ value = self._parse_constant_expression(inTemplate=True)
+ self.skip_ws()
+ if self.skip_string_and_ws('...'):
+ packExpansion = True
+ parsedEnd = True
+ if not self.skip_string('>'):
+ self.fail('Expected ">" after "..." in template argument list.')
+ elif self.skip_string('>'):
+ parsedEnd = True
+ elif self.skip_string(','):
+ parsedComma = True
+ else:
+ self.fail('Expected "...>", ">" or "," in template argument list.')
+ templateArgs.append(ASTTemplateArgConstant(value))
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If non-type argument"))
+ header = "Error in parsing template argument list."
+ raise self._make_multi_error(prevErrors, header) from e
+ if parsedEnd:
+ assert not parsedComma
+ break
+ assert not packExpansion
+ return ASTTemplateArgs(templateArgs, packExpansion)
+
+ def _parse_nested_name(self, memberPointer: bool = False) -> ASTNestedName:
+ names: list[ASTNestedNameElement] = []
+ templates: list[bool] = []
+
+ self.skip_ws()
+ rooted = False
+ if self.skip_string('::'):
+ rooted = True
+ while 1:
+ self.skip_ws()
+ if len(names) > 0:
+ template = self.skip_word_and_ws('template')
+ else:
+ template = False
+ templates.append(template)
+ identOrOp: ASTIdentifier | ASTOperator = None
+ if self.skip_word_and_ws('operator'):
+ identOrOp = self._parse_operator()
+ else:
+ if not self.match(identifier_re):
+ if memberPointer and len(names) > 0:
+ templates.pop()
+ break
+ self.fail("Expected identifier in nested name.")
+ identifier = self.matched_text
+ # make sure there isn't a keyword
+ if identifier in _keywords:
+ self.fail("Expected identifier in nested name, "
+ "got keyword: %s" % identifier)
+ identOrOp = ASTIdentifier(identifier)
+ # try greedily to get template arguments,
+ # but otherwise a < might be because we are in an expression
+ pos = self.pos
+ try:
+ templateArgs = self._parse_template_argument_list()
+ except DefinitionError as ex:
+ self.pos = pos
+ templateArgs = None
+ self.otherErrors.append(ex)
+ names.append(ASTNestedNameElement(identOrOp, templateArgs))
+
+ self.skip_ws()
+ if not self.skip_string('::'):
+ if memberPointer:
+ self.fail("Expected '::' in pointer to member (function).")
+ break
+ return ASTNestedName(names, templates, rooted)
+
+ # ==========================================================================
+
+ def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental:
+ modifier: str | None = None
+ signedness: str | None = None
+ width: list[str] = []
+ typ: str | None = None
+ names: list[str] = [] # the parsed sequence
+
+ self.skip_ws()
+ while self.match(_simple_type_specifiers_re):
+ t = self.matched_text
+ names.append(t)
+ if t in ('auto', 'void', 'bool',
+ 'char', 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
+ 'int', '__int64', '__int128',
+ 'float', 'double',
+ '__float80', '_Float64x', '__float128', '_Float128'):
+ if typ is not None:
+ self.fail(f"Can not have both {t} and {typ}.")
+ typ = t
+ elif t in ('signed', 'unsigned'):
+ if signedness is not None:
+ self.fail(f"Can not have both {t} and {signedness}.")
+ signedness = t
+ elif t == 'short':
+ if len(width) != 0:
+ self.fail(f"Can not have both {t} and {width[0]}.")
+ width.append(t)
+ elif t == 'long':
+ if len(width) != 0 and width[0] != 'long':
+ self.fail(f"Can not have both {t} and {width[0]}.")
+ width.append(t)
+ elif t in ('_Imaginary', '_Complex'):
+ if modifier is not None:
+ self.fail(f"Can not have both {t} and {modifier}.")
+ modifier = t
+ self.skip_ws()
+ if len(names) == 0:
+ return None
+
+ if typ in ('auto', 'void', 'bool',
+ 'wchar_t', 'char8_t', 'char16_t', 'char32_t',
+ '__float80', '_Float64x', '__float128', '_Float128'):
+ if modifier is not None:
+ self.fail(f"Can not have both {typ} and {modifier}.")
+ if signedness is not None:
+ self.fail(f"Can not have both {typ} and {signedness}.")
+ if len(width) != 0:
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ elif typ == 'char':
+ if modifier is not None:
+ self.fail(f"Can not have both {typ} and {modifier}.")
+ if len(width) != 0:
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ elif typ == 'int':
+ if modifier is not None:
+ self.fail(f"Can not have both {typ} and {modifier}.")
+ elif typ in ('__int64', '__int128'):
+ if modifier is not None:
+ self.fail(f"Can not have both {typ} and {modifier}.")
+ if len(width) != 0:
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ elif typ == 'float':
+ if signedness is not None:
+ self.fail(f"Can not have both {typ} and {signedness}.")
+ if len(width) != 0:
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ elif typ == 'double':
+ if signedness is not None:
+ self.fail(f"Can not have both {typ} and {signedness}.")
+ if len(width) > 1:
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ if len(width) == 1 and width[0] != 'long':
+ self.fail(f"Can not have both {typ} and {' '.join(width)}.")
+ elif typ is None:
+ if modifier is not None:
+ self.fail(f"Can not have {modifier} without a floating point type.")
+ else:
+ msg = f'Unhandled type {typ}'
+ raise AssertionError(msg)
+
+ canonNames: list[str] = []
+ if modifier is not None:
+ canonNames.append(modifier)
+ if signedness is not None:
+ canonNames.append(signedness)
+ canonNames.extend(width)
+ if typ is not None:
+ canonNames.append(typ)
+ return ASTTrailingTypeSpecFundamental(names, canonNames)
+
+ def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
+ # fundamental types, https://en.cppreference.com/w/cpp/language/type
+ # and extensions
+ self.skip_ws()
+ res = self._parse_simple_type_specifiers()
+ if res is not None:
+ return res
+
+ # decltype
+ self.skip_ws()
+ if self.skip_word_and_ws('decltype'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expected '(' after 'decltype'.")
+ if self.skip_word_and_ws('auto'):
+ if not self.skip_string(')'):
+ self.fail("Expected ')' after 'decltype(auto'.")
+ return ASTTrailingTypeSpecDecltypeAuto()
+ expr = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' after 'decltype(<expr>'.")
+ return ASTTrailingTypeSpecDecltype(expr)
+
+ # prefixed
+ prefix = None
+ self.skip_ws()
+ for k in ('class', 'struct', 'enum', 'union', 'typename'):
+ if self.skip_word_and_ws(k):
+ prefix = k
+ break
+ nestedName = self._parse_nested_name()
+ self.skip_ws()
+ placeholderType = None
+ if self.skip_word('auto'):
+ placeholderType = 'auto'
+ elif self.skip_word_and_ws('decltype'):
+ if not self.skip_string_and_ws('('):
+ self.fail("Expected '(' after 'decltype' in placeholder type specifier.")
+ if not self.skip_word_and_ws('auto'):
+ self.fail("Expected 'auto' after 'decltype(' in placeholder type specifier.")
+ if not self.skip_string_and_ws(')'):
+ self.fail("Expected ')' after 'decltype(auto' in placeholder type specifier.")
+ placeholderType = 'decltype(auto)'
+ return ASTTrailingTypeSpecName(prefix, nestedName, placeholderType)
+
+ def _parse_parameters_and_qualifiers(self, paramMode: str) -> ASTParametersQualifiers:
+ if paramMode == 'new':
+ return None
+ self.skip_ws()
+ if not self.skip_string('('):
+ if paramMode == 'function':
+ self.fail('Expecting "(" in parameters-and-qualifiers.')
+ else:
+ return None
+ args = []
+ self.skip_ws()
+ if not self.skip_string(')'):
+ while 1:
+ self.skip_ws()
+ if self.skip_string('...'):
+ args.append(ASTFunctionParameter(None, True))
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail('Expected ")" after "..." in '
+ 'parameters-and-qualifiers.')
+ break
+ # note: it seems that function arguments can always be named,
+ # even in function pointers and similar.
+ arg = self._parse_type_with_init(outer=None, named='single')
+ # TODO: parse default parameters # TODO: didn't we just do that?
+ args.append(ASTFunctionParameter(arg))
+
+ self.skip_ws()
+ if self.skip_string(','):
+ continue
+ if self.skip_string(')'):
+ break
+ self.fail('Expecting "," or ")" in parameters-and-qualifiers, '
+ f'got "{self.current_char}".')
+
+ self.skip_ws()
+ const = self.skip_word_and_ws('const')
+ volatile = self.skip_word_and_ws('volatile')
+ if not const: # the can be permuted
+ const = self.skip_word_and_ws('const')
+
+ refQual = None
+ if self.skip_string('&&'):
+ refQual = '&&'
+ if not refQual and self.skip_string('&'):
+ refQual = '&'
+
+ exceptionSpec = None
+ self.skip_ws()
+ if self.skip_string('noexcept'):
+ if self.skip_string_and_ws('('):
+ expr = self._parse_constant_expression(False)
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expecting ')' to end 'noexcept'.")
+ exceptionSpec = ASTNoexceptSpec(expr)
+ else:
+ exceptionSpec = ASTNoexceptSpec(None)
+
+ self.skip_ws()
+ if self.skip_string('->'):
+ trailingReturn = self._parse_type(named=False)
+ else:
+ trailingReturn = None
+
+ self.skip_ws()
+ override = self.skip_word_and_ws('override')
+ final = self.skip_word_and_ws('final')
+ if not override:
+ override = self.skip_word_and_ws(
+ 'override') # they can be permuted
+
+ attrs = self._parse_attribute_list()
+
+ self.skip_ws()
+ initializer = None
+ # if this is a function pointer we should not swallow an initializer
+ if paramMode == 'function' and self.skip_string('='):
+ self.skip_ws()
+ valid = ('0', 'delete', 'default')
+ for w in valid:
+ if self.skip_word_and_ws(w):
+ initializer = w
+ break
+ if not initializer:
+ self.fail(
+ 'Expected "%s" in initializer-specifier.'
+ % '" or "'.join(valid))
+
+ return ASTParametersQualifiers(
+ args, volatile, const, refQual, exceptionSpec, trailingReturn,
+ override, final, attrs, initializer)
+
+ def _parse_decl_specs_simple(self, outer: str, typed: bool) -> ASTDeclSpecsSimple:
+ """Just parse the simple ones."""
+ storage = None
+ threadLocal = None
+ inline = None
+ virtual = None
+ explicitSpec = None
+ consteval = None
+ constexpr = None
+ constinit = None
+ volatile = None
+ const = None
+ friend = None
+ attrs = []
+ while 1: # accept any permutation of a subset of some decl-specs
+ self.skip_ws()
+ if not const and typed:
+ const = self.skip_word('const')
+ if const:
+ continue
+ if not volatile and typed:
+ volatile = self.skip_word('volatile')
+ if volatile:
+ continue
+ if not storage:
+ if outer in ('member', 'function'):
+ if self.skip_word('static'):
+ storage = 'static'
+ continue
+ if self.skip_word('extern'):
+ storage = 'extern'
+ continue
+ if outer == 'member':
+ if self.skip_word('mutable'):
+ storage = 'mutable'
+ continue
+ if self.skip_word('register'):
+ storage = 'register'
+ continue
+ if not inline and outer in ('function', 'member'):
+ inline = self.skip_word('inline')
+ if inline:
+ continue
+ if not constexpr and outer in ('member', 'function'):
+ constexpr = self.skip_word("constexpr")
+ if constexpr:
+ continue
+
+ if outer == 'member':
+ if not constinit:
+ constinit = self.skip_word('constinit')
+ if constinit:
+ continue
+ if not threadLocal:
+ threadLocal = self.skip_word('thread_local')
+ if threadLocal:
+ continue
+ if outer == 'function':
+ if not consteval:
+ consteval = self.skip_word('consteval')
+ if consteval:
+ continue
+ if not friend:
+ friend = self.skip_word('friend')
+ if friend:
+ continue
+ if not virtual:
+ virtual = self.skip_word('virtual')
+ if virtual:
+ continue
+ if not explicitSpec:
+ explicit = self.skip_word_and_ws('explicit')
+ if explicit:
+ expr: ASTExpression = None
+ if self.skip_string('('):
+ expr = self._parse_constant_expression(inTemplate=False)
+ if not expr:
+ self.fail("Expected constant expression after '('" +
+ " in explicit specifier.")
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' to end explicit specifier.")
+ explicitSpec = ASTExplicitSpec(expr)
+ continue
+ attr = self._parse_attribute()
+ if attr:
+ attrs.append(attr)
+ continue
+ break
+ return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual,
+ explicitSpec, consteval, constexpr, constinit,
+ volatile, const, friend, ASTAttributeList(attrs))
+
+ def _parse_decl_specs(self, outer: str, typed: bool = True) -> ASTDeclSpecs:
+ if outer:
+ if outer not in ('type', 'member', 'function', 'templateParam'):
+ raise Exception('Internal error, unknown outer "%s".' % outer)
+ """
+ storage-class-specifier function-specifier "constexpr"
+ "volatile" "const" trailing-type-specifier
+
+ storage-class-specifier ->
+ "static" (only for member_object and function_object)
+ | "register"
+
+ function-specifier -> "inline" | "virtual" | "explicit" (only for
+ function_object)
+
+ "constexpr" (only for member_object and function_object)
+ """
+ leftSpecs = self._parse_decl_specs_simple(outer, typed)
+ rightSpecs = None
+
+ if typed:
+ trailing = self._parse_trailing_type_spec()
+ rightSpecs = self._parse_decl_specs_simple(outer, typed)
+ else:
+ trailing = None
+ return ASTDeclSpecs(outer, leftSpecs, rightSpecs, trailing)
+
+ def _parse_declarator_name_suffix(
+ self, named: bool | str, paramMode: str, typed: bool,
+ ) -> ASTDeclaratorNameParamQual | ASTDeclaratorNameBitField:
+ # now we should parse the name, and then suffixes
+ if named == 'maybe':
+ pos = self.pos
+ try:
+ declId = self._parse_nested_name()
+ except DefinitionError:
+ self.pos = pos
+ declId = None
+ elif named == 'single':
+ if self.match(identifier_re):
+ identifier = ASTIdentifier(self.matched_text)
+ nne = ASTNestedNameElement(identifier, None)
+ declId = ASTNestedName([nne], [False], rooted=False)
+ # if it's a member pointer, we may have '::', which should be an error
+ self.skip_ws()
+ if self.current_char == ':':
+ self.fail("Unexpected ':' after identifier.")
+ else:
+ declId = None
+ elif named:
+ declId = self._parse_nested_name()
+ else:
+ declId = None
+ arrayOps = []
+ while 1:
+ self.skip_ws()
+ if typed and self.skip_string('['):
+ self.skip_ws()
+ if self.skip_string(']'):
+ arrayOps.append(ASTArray(None))
+ continue
+
+ def parser() -> ASTExpression:
+ return self._parse_expression()
+ value = self._parse_expression_fallback([']'], parser)
+ if not self.skip_string(']'):
+ self.fail("Expected ']' in end of array operator.")
+ arrayOps.append(ASTArray(value))
+ continue
+ break
+ paramQual = self._parse_parameters_and_qualifiers(paramMode)
+ if paramQual is None and len(arrayOps) == 0:
+ # perhaps a bit-field
+ if named and paramMode == 'type' and typed:
+ self.skip_ws()
+ if self.skip_string(':'):
+ size = self._parse_constant_expression(inTemplate=False)
+ return ASTDeclaratorNameBitField(declId=declId, size=size)
+ return ASTDeclaratorNameParamQual(declId=declId, arrayOps=arrayOps,
+ paramQual=paramQual)
+
+ def _parse_declarator(self, named: bool | str, paramMode: str,
+ typed: bool = True,
+ ) -> ASTDeclarator:
+ # 'typed' here means 'parse return type stuff'
+ if paramMode not in ('type', 'function', 'operatorCast', 'new'):
+ raise Exception(
+ "Internal error, unknown paramMode '%s'." % paramMode)
+ prevErrors = []
+ self.skip_ws()
+ if typed and self.skip_string('*'):
+ self.skip_ws()
+ volatile = False
+ const = False
+ attrList = []
+ while 1:
+ if not volatile:
+ volatile = self.skip_word_and_ws('volatile')
+ if volatile:
+ continue
+ if not const:
+ const = self.skip_word_and_ws('const')
+ if const:
+ continue
+ attr = self._parse_attribute()
+ if attr is not None:
+ attrList.append(attr)
+ continue
+ break
+ next = self._parse_declarator(named, paramMode, typed)
+ return ASTDeclaratorPtr(next=next, volatile=volatile, const=const,
+ attrs=ASTAttributeList(attrList))
+ # TODO: shouldn't we parse an R-value ref here first?
+ if typed and self.skip_string("&"):
+ attrs = self._parse_attribute_list()
+ next = self._parse_declarator(named, paramMode, typed)
+ return ASTDeclaratorRef(next=next, attrs=attrs)
+ if typed and self.skip_string("..."):
+ next = self._parse_declarator(named, paramMode, False)
+ return ASTDeclaratorParamPack(next=next)
+ if typed and self.current_char == '(': # note: peeking, not skipping
+ if paramMode == "operatorCast":
+ # TODO: we should be able to parse cast operators which return
+ # function pointers. For now, just hax it and ignore.
+ return ASTDeclaratorNameParamQual(declId=None, arrayOps=[],
+ paramQual=None)
+ # maybe this is the beginning of params and quals,try that first,
+ # otherwise assume it's noptr->declarator > ( ptr-declarator )
+ pos = self.pos
+ try:
+ # assume this is params and quals
+ res = self._parse_declarator_name_suffix(named, paramMode,
+ typed)
+ return res
+ except DefinitionError as exParamQual:
+ prevErrors.append((exParamQual,
+ "If declarator-id with parameters-and-qualifiers"))
+ self.pos = pos
+ try:
+ assert self.current_char == '('
+ self.skip_string('(')
+ # TODO: hmm, if there is a name, it must be in inner, right?
+ # TODO: hmm, if there must be parameters, they must be
+ # inside, right?
+ inner = self._parse_declarator(named, paramMode, typed)
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in \"( ptr-declarator )\"")
+ next = self._parse_declarator(named=False,
+ paramMode="type",
+ typed=typed)
+ return ASTDeclaratorParen(inner=inner, next=next)
+ except DefinitionError as exNoPtrParen:
+ self.pos = pos
+ prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
+ header = "Error in declarator"
+ raise self._make_multi_error(prevErrors, header) from exNoPtrParen
+ if typed: # pointer to member
+ pos = self.pos
+ try:
+ name = self._parse_nested_name(memberPointer=True)
+ self.skip_ws()
+ if not self.skip_string('*'):
+ self.fail("Expected '*' in pointer to member declarator.")
+ self.skip_ws()
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If pointer to member declarator"))
+ else:
+ volatile = False
+ const = False
+ while 1:
+ if not volatile:
+ volatile = self.skip_word_and_ws('volatile')
+ if volatile:
+ continue
+ if not const:
+ const = self.skip_word_and_ws('const')
+ if const:
+ continue
+ break
+ next = self._parse_declarator(named, paramMode, typed)
+ return ASTDeclaratorMemPtr(name, const, volatile, next=next)
+ pos = self.pos
+ try:
+ res = self._parse_declarator_name_suffix(named, paramMode, typed)
+ # this is a heuristic for error messages, for when there is a < after a
+ # nested name, but it was not a successful template argument list
+ if self.current_char == '<':
+ self.otherErrors.append(self._make_multi_error(prevErrors, ""))
+ return res
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If declarator-id"))
+ header = "Error in declarator or parameters-and-qualifiers"
+ raise self._make_multi_error(prevErrors, header) from e
+
+ def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
+ ) -> ASTInitializer:
+ # initializer # global vars
+ # -> brace-or-equal-initializer
+ # | '(' expression-list ')'
+ #
+ # brace-or-equal-initializer # member vars
+ # -> '=' initializer-clause
+ # | braced-init-list
+ #
+ # initializer-clause # function params, non-type template params (with '=' in front)
+ # -> assignment-expression
+ # | braced-init-list
+ #
+ # we don't distinguish between global and member vars, so disallow paren:
+ #
+ # -> braced-init-list # var only
+ # | '=' assignment-expression
+ # | '=' braced-init-list
+ self.skip_ws()
+ if outer == 'member':
+ bracedInit = self._parse_braced_init_list()
+ if bracedInit is not None:
+ return ASTInitializer(bracedInit, hasAssign=False)
+
+ if not self.skip_string('='):
+ return None
+
+ bracedInit = self._parse_braced_init_list()
+ if bracedInit is not None:
+ return ASTInitializer(bracedInit)
+
+ if outer == 'member':
+ fallbackEnd: list[str] = []
+ elif outer == 'templateParam':
+ fallbackEnd = [',', '>']
+ elif outer is None: # function parameter
+ fallbackEnd = [',', ')']
+ else:
+ self.fail("Internal error, initializer for outer '%s' not "
+ "implemented." % outer)
+
+ inTemplate = outer == 'templateParam'
+
+ def parser() -> ASTExpression:
+ return self._parse_assignment_expression(inTemplate=inTemplate)
+ value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback)
+ return ASTInitializer(value)
+
+ def _parse_type(self, named: bool | str, outer: str | None = None) -> ASTType:
+ """
+ named=False|'maybe'|True: 'maybe' is e.g., for function objects which
+ doesn't need to name the arguments
+
+ outer == operatorCast: annoying case, we should not take the params
+ """
+ if outer: # always named
+ if outer not in ('type', 'member', 'function',
+ 'operatorCast', 'templateParam'):
+ raise Exception('Internal error, unknown outer "%s".' % outer)
+ if outer != 'operatorCast':
+ assert named
+ if outer in ('type', 'function'):
+ # We allow type objects to just be a name.
+ # Some functions don't have normal return types: constructors,
+ # destructors, cast operators
+ prevErrors = []
+ startPos = self.pos
+ # first try without the type
+ try:
+ declSpecs = self._parse_decl_specs(outer=outer, typed=False)
+ decl = self._parse_declarator(named=True, paramMode=outer,
+ typed=False)
+ mustEnd = True
+ if outer == 'function':
+ # Allow trailing requires on functions.
+ self.skip_ws()
+ if re.compile(r'requires\b').match(self.definition, self.pos):
+ mustEnd = False
+ if mustEnd:
+ self.assert_end(allowSemicolon=True)
+ except DefinitionError as exUntyped:
+ if outer == 'type':
+ desc = "If just a name"
+ elif outer == 'function':
+ desc = "If the function has no return type"
+ else:
+ raise AssertionError from exUntyped
+ prevErrors.append((exUntyped, desc))
+ self.pos = startPos
+ try:
+ declSpecs = self._parse_decl_specs(outer=outer)
+ decl = self._parse_declarator(named=True, paramMode=outer)
+ except DefinitionError as exTyped:
+ self.pos = startPos
+ if outer == 'type':
+ desc = "If typedef-like declaration"
+ elif outer == 'function':
+ desc = "If the function has a return type"
+ else:
+ raise AssertionError from exUntyped
+ prevErrors.append((exTyped, desc))
+ # Retain the else branch for easier debugging.
+ # TODO: it would be nice to save the previous stacktrace
+ # and output it here.
+ if True:
+ if outer == 'type':
+ header = "Type must be either just a name or a "
+ header += "typedef-like declaration."
+ elif outer == 'function':
+ header = "Error when parsing function declaration."
+ else:
+ raise AssertionError from exUntyped
+ raise self._make_multi_error(prevErrors, header) from exTyped
+ else: # NoQA: RET506
+ # For testing purposes.
+ # do it again to get the proper traceback (how do you
+ # reliably save a traceback when an exception is
+ # constructed?)
+ self.pos = startPos
+ typed = True
+ declSpecs = self._parse_decl_specs(outer=outer, typed=typed)
+ decl = self._parse_declarator(named=True, paramMode=outer,
+ typed=typed)
+ else:
+ paramMode = 'type'
+ if outer == 'member':
+ named = True
+ elif outer == 'operatorCast':
+ paramMode = 'operatorCast'
+ outer = None
+ elif outer == 'templateParam':
+ named = 'single'
+ declSpecs = self._parse_decl_specs(outer=outer)
+ decl = self._parse_declarator(named=named, paramMode=paramMode)
+ return ASTType(declSpecs, decl)
+
+ def _parse_type_with_init(
+ self, named: bool | str,
+ outer: str) -> ASTTypeWithInit | ASTTemplateParamConstrainedTypeWithInit:
+ if outer:
+ assert outer in ('type', 'member', 'function', 'templateParam')
+ type = self._parse_type(outer=outer, named=named)
+ if outer != 'templateParam':
+ init = self._parse_initializer(outer=outer)
+ return ASTTypeWithInit(type, init)
+ # it could also be a constrained type parameter, e.g., C T = int&
+ pos = self.pos
+ eExpr = None
+ try:
+ init = self._parse_initializer(outer=outer, allowFallback=False)
+ # note: init may be None if there is no =
+ if init is None:
+ return ASTTypeWithInit(type, None)
+ # we parsed an expression, so we must have a , or a >,
+ # otherwise the expression didn't get everything
+ self.skip_ws()
+ if self.current_char != ',' and self.current_char != '>':
+ # pretend it didn't happen
+ self.pos = pos
+ init = None
+ else:
+ # we assume that it was indeed an expression
+ return ASTTypeWithInit(type, init)
+ except DefinitionError as e:
+ self.pos = pos
+ eExpr = e
+ if not self.skip_string("="):
+ return ASTTypeWithInit(type, None)
+ try:
+ typeInit = self._parse_type(named=False, outer=None)
+ return ASTTemplateParamConstrainedTypeWithInit(type, typeInit)
+ except DefinitionError as eType:
+ if eExpr is None:
+ raise
+ errs = []
+ errs.append((eExpr, "If default template argument is an expression"))
+ errs.append((eType, "If default template argument is a type"))
+ msg = "Error in non-type template parameter"
+ msg += " or constrained template parameter."
+ raise self._make_multi_error(errs, msg) from eType
+
+ def _parse_type_using(self) -> ASTTypeUsing:
+ name = self._parse_nested_name()
+ self.skip_ws()
+ if not self.skip_string('='):
+ return ASTTypeUsing(name, None)
+ type = self._parse_type(False, None)
+ return ASTTypeUsing(name, type)
+
+ def _parse_concept(self) -> ASTConcept:
+ nestedName = self._parse_nested_name()
+ self.skip_ws()
+ initializer = self._parse_initializer('member')
+ return ASTConcept(nestedName, initializer)
+
+ def _parse_class(self) -> ASTClass:
+ attrs = self._parse_attribute_list()
+ name = self._parse_nested_name()
+ self.skip_ws()
+ final = self.skip_word_and_ws('final')
+ bases = []
+ self.skip_ws()
+ if self.skip_string(':'):
+ while 1:
+ self.skip_ws()
+ visibility = None
+ virtual = False
+ pack = False
+ if self.skip_word_and_ws('virtual'):
+ virtual = True
+ if self.match(_visibility_re):
+ visibility = self.matched_text
+ self.skip_ws()
+ if not virtual and self.skip_word_and_ws('virtual'):
+ virtual = True
+ baseName = self._parse_nested_name()
+ self.skip_ws()
+ pack = self.skip_string('...')
+ bases.append(ASTBaseClass(baseName, visibility, virtual, pack))
+ self.skip_ws()
+ if self.skip_string(','):
+ continue
+ break
+ return ASTClass(name, final, bases, attrs)
+
+ def _parse_union(self) -> ASTUnion:
+ attrs = self._parse_attribute_list()
+ name = self._parse_nested_name()
+ return ASTUnion(name, attrs)
+
+ def _parse_enum(self) -> ASTEnum:
+ scoped = None # is set by CPPEnumObject
+ attrs = self._parse_attribute_list()
+ name = self._parse_nested_name()
+ self.skip_ws()
+ underlyingType = None
+ if self.skip_string(':'):
+ underlyingType = self._parse_type(named=False)
+ return ASTEnum(name, scoped, underlyingType, attrs)
+
+ def _parse_enumerator(self) -> ASTEnumerator:
+ name = self._parse_nested_name()
+ attrs = self._parse_attribute_list()
+ self.skip_ws()
+ init = None
+ if self.skip_string('='):
+ self.skip_ws()
+
+ def parser() -> ASTExpression:
+ return self._parse_constant_expression(inTemplate=False)
+ initVal = self._parse_expression_fallback([], parser)
+ init = ASTInitializer(initVal)
+ return ASTEnumerator(name, init, attrs)
+
+ # ==========================================================================
+
+ def _parse_template_parameter(self) -> ASTTemplateParam:
+ self.skip_ws()
+ if self.skip_word('template'):
+ # declare a template template parameter
+ nestedParams = self._parse_template_parameter_list()
+ else:
+ nestedParams = None
+
+ pos = self.pos
+ try:
+ # Unconstrained type parameter or template type parameter
+ key = None
+ self.skip_ws()
+ if self.skip_word_and_ws('typename'):
+ key = 'typename'
+ elif self.skip_word_and_ws('class'):
+ key = 'class'
+ elif nestedParams:
+ self.fail("Expected 'typename' or 'class' after "
+ "template template parameter list.")
+ else:
+ self.fail("Expected 'typename' or 'class' in the "
+ "beginning of template type parameter.")
+ self.skip_ws()
+ parameterPack = self.skip_string('...')
+ self.skip_ws()
+ if self.match(identifier_re):
+ identifier = ASTIdentifier(self.matched_text)
+ else:
+ identifier = None
+ self.skip_ws()
+ if not parameterPack and self.skip_string('='):
+ default = self._parse_type(named=False, outer=None)
+ else:
+ default = None
+ if self.current_char not in ',>':
+ self.fail('Expected "," or ">" after (template) type parameter.')
+ data = ASTTemplateKeyParamPackIdDefault(key, identifier,
+ parameterPack, default)
+ if nestedParams:
+ return ASTTemplateParamTemplateType(nestedParams, data)
+ else:
+ return ASTTemplateParamType(data)
+ except DefinitionError as eType:
+ if nestedParams:
+ raise
+ try:
+ # non-type parameter or constrained type parameter
+ self.pos = pos
+ param = self._parse_type_with_init('maybe', 'templateParam')
+ self.skip_ws()
+ parameterPack = self.skip_string('...')
+ return ASTTemplateParamNonType(param, parameterPack)
+ except DefinitionError as eNonType:
+ self.pos = pos
+ header = "Error when parsing template parameter."
+ errs = []
+ errs.append(
+ (eType, "If unconstrained type parameter or template type parameter"))
+ errs.append(
+ (eNonType, "If constrained type parameter or non-type parameter"))
+ raise self._make_multi_error(errs, header) from None
+
+ def _parse_template_parameter_list(self) -> ASTTemplateParams:
+ # only: '<' parameter-list '>'
+ # we assume that 'template' has just been parsed
+ templateParams: list[ASTTemplateParam] = []
+ self.skip_ws()
+ if not self.skip_string("<"):
+ self.fail("Expected '<' after 'template'")
+ while 1:
+ pos = self.pos
+ err = None
+ try:
+ param = self._parse_template_parameter()
+ templateParams.append(param)
+ except DefinitionError as eParam:
+ self.pos = pos
+ err = eParam
+ self.skip_ws()
+ if self.skip_string('>'):
+ requiresClause = self._parse_requires_clause()
+ return ASTTemplateParams(templateParams, requiresClause)
+ elif self.skip_string(','):
+ continue
+ else:
+ header = "Error in template parameter list."
+ errs = []
+ if err:
+ errs.append((err, "If parameter"))
+ try:
+ self.fail('Expected "," or ">".')
+ except DefinitionError as e:
+ errs.append((e, "If no parameter"))
+ logger.debug(errs)
+ raise self._make_multi_error(errs, header)
+
+ def _parse_template_introduction(self) -> ASTTemplateIntroduction:
+ pos = self.pos
+ try:
+ concept = self._parse_nested_name()
+ except Exception:
+ self.pos = pos
+ return None
+ self.skip_ws()
+ if not self.skip_string('{'):
+ self.pos = pos
+ return None
+
+ # for sure it must be a template introduction now
+ params = []
+ while 1:
+ self.skip_ws()
+ parameterPack = self.skip_string('...')
+ self.skip_ws()
+ if not self.match(identifier_re):
+ self.fail("Expected identifier in template introduction list.")
+ txt_identifier = self.matched_text
+ # make sure there isn't a keyword
+ if txt_identifier in _keywords:
+ self.fail("Expected identifier in template introduction list, "
+ "got keyword: %s" % txt_identifier)
+ identifier = ASTIdentifier(txt_identifier)
+ params.append(ASTTemplateIntroductionParameter(identifier, parameterPack))
+
+ self.skip_ws()
+ if self.skip_string('}'):
+ break
+ if self.skip_string(','):
+ continue
+ self.fail('Error in template introduction list. Expected ",", or "}".')
+ return ASTTemplateIntroduction(concept, params)
+
+ def _parse_requires_clause(self) -> ASTRequiresClause | None:
+ # requires-clause -> 'requires' constraint-logical-or-expression
+ # constraint-logical-or-expression
+ # -> constraint-logical-and-expression
+ # | constraint-logical-or-expression '||' constraint-logical-and-expression
+ # constraint-logical-and-expression
+ # -> primary-expression
+ # | constraint-logical-and-expression '&&' primary-expression
+ self.skip_ws()
+ if not self.skip_word('requires'):
+ return None
+
+ def parse_and_expr(self: DefinitionParser) -> ASTExpression:
+ andExprs = []
+ ops = []
+ andExprs.append(self._parse_primary_expression())
+ while True:
+ self.skip_ws()
+ oneMore = False
+ if self.skip_string('&&'):
+ oneMore = True
+ ops.append('&&')
+ elif self.skip_word('and'):
+ oneMore = True
+ ops.append('and')
+ if not oneMore:
+ break
+ andExprs.append(self._parse_primary_expression())
+ if len(andExprs) == 1:
+ return andExprs[0]
+ else:
+ return ASTBinOpExpr(andExprs, ops)
+
+ orExprs = []
+ ops = []
+ orExprs.append(parse_and_expr(self))
+ while True:
+ self.skip_ws()
+ oneMore = False
+ if self.skip_string('||'):
+ oneMore = True
+ ops.append('||')
+ elif self.skip_word('or'):
+ oneMore = True
+ ops.append('or')
+ if not oneMore:
+ break
+ orExprs.append(parse_and_expr(self))
+ if len(orExprs) == 1:
+ return ASTRequiresClause(orExprs[0])
+ else:
+ return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
+
+ def _parse_template_declaration_prefix(self, objectType: str,
+ ) -> ASTTemplateDeclarationPrefix | None:
+ templates: list[ASTTemplateParams | ASTTemplateIntroduction] = []
+ while 1:
+ self.skip_ws()
+ # the saved position is only used to provide a better error message
+ params: ASTTemplateParams | ASTTemplateIntroduction = None
+ pos = self.pos
+ if self.skip_word("template"):
+ try:
+ params = self._parse_template_parameter_list()
+ except DefinitionError as e:
+ if objectType == 'member' and len(templates) == 0:
+ return ASTTemplateDeclarationPrefix(None)
+ else:
+ raise e
+ if objectType == 'concept' and params.requiresClause is not None:
+ self.fail('requires-clause not allowed for concept')
+ else:
+ params = self._parse_template_introduction()
+ if not params:
+ break
+ if objectType == 'concept' and len(templates) > 0:
+ self.pos = pos
+ self.fail("More than 1 template parameter list for concept.")
+ templates.append(params)
+ if len(templates) == 0 and objectType == 'concept':
+ self.fail('Missing template parameter list for concept.')
+ if len(templates) == 0:
+ return None
+ else:
+ return ASTTemplateDeclarationPrefix(templates)
+
+ def _check_template_consistency(self, nestedName: ASTNestedName,
+ templatePrefix: ASTTemplateDeclarationPrefix,
+ fullSpecShorthand: bool, isMember: bool = False,
+ ) -> ASTTemplateDeclarationPrefix:
+ numArgs = nestedName.num_templates()
+ isMemberInstantiation = False
+ if not templatePrefix:
+ numParams = 0
+ else:
+ if isMember and templatePrefix.templates is None:
+ numParams = 0
+ isMemberInstantiation = True
+ else:
+ numParams = len(templatePrefix.templates)
+ if numArgs + 1 < numParams:
+ self.fail("Too few template argument lists comapred to parameter"
+ " lists. Argument lists: %d, Parameter lists: %d."
+ % (numArgs, numParams))
+ if numArgs > numParams:
+ numExtra = numArgs - numParams
+ if not fullSpecShorthand and not isMemberInstantiation:
+ msg = "Too many template argument lists compared to parameter" \
+ " lists. Argument lists: %d, Parameter lists: %d," \
+ " Extra empty parameters lists prepended: %d." \
+ % (numArgs, numParams, numExtra)
+ msg += " Declaration:\n\t"
+ if templatePrefix:
+ msg += "%s\n\t" % templatePrefix
+ msg += str(nestedName)
+ self.warn(msg)
+
+ newTemplates: list[ASTTemplateParams | ASTTemplateIntroduction] = []
+ for _i in range(numExtra):
+ newTemplates.append(ASTTemplateParams([], requiresClause=None))
+ if templatePrefix and not isMemberInstantiation:
+ newTemplates.extend(templatePrefix.templates)
+ templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
+ return templatePrefix
+
+ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
+ if objectType not in ('class', 'union', 'function', 'member', 'type',
+ 'concept', 'enum', 'enumerator'):
+ raise Exception('Internal error, unknown objectType "%s".' % objectType)
+ if directiveType not in ('class', 'struct', 'union', 'function', 'member', 'var',
+ 'type', 'concept',
+ 'enum', 'enum-struct', 'enum-class', 'enumerator'):
+ raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
+ visibility = None
+ templatePrefix = None
+ trailingRequiresClause = None
+ declaration: Any = None
+
+ self.skip_ws()
+ if self.match(_visibility_re):
+ visibility = self.matched_text
+
+ if objectType in ('type', 'concept', 'member', 'function', 'class', 'union'):
+ templatePrefix = self._parse_template_declaration_prefix(objectType)
+
+ if objectType == 'type':
+ prevErrors = []
+ pos = self.pos
+ try:
+ if not templatePrefix:
+ declaration = self._parse_type(named=True, outer='type')
+ except DefinitionError as e:
+ prevErrors.append((e, "If typedef-like declaration"))
+ self.pos = pos
+ pos = self.pos
+ try:
+ if not declaration:
+ declaration = self._parse_type_using()
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If type alias or template alias"))
+ header = "Error in type declaration."
+ raise self._make_multi_error(prevErrors, header) from e
+ elif objectType == 'concept':
+ declaration = self._parse_concept()
+ elif objectType == 'member':
+ declaration = self._parse_type_with_init(named=True, outer='member')
+ elif objectType == 'function':
+ declaration = self._parse_type(named=True, outer='function')
+ trailingRequiresClause = self._parse_requires_clause()
+ elif objectType == 'class':
+ declaration = self._parse_class()
+ elif objectType == 'union':
+ declaration = self._parse_union()
+ elif objectType == 'enum':
+ declaration = self._parse_enum()
+ elif objectType == 'enumerator':
+ declaration = self._parse_enumerator()
+ else:
+ raise AssertionError
+ templatePrefix = self._check_template_consistency(declaration.name,
+ templatePrefix,
+ fullSpecShorthand=False,
+ isMember=objectType == 'member')
+ self.skip_ws()
+ semicolon = self.skip_string(';')
+ return ASTDeclaration(objectType, directiveType, visibility,
+ templatePrefix, declaration,
+ trailingRequiresClause, semicolon)
+
+ def parse_namespace_object(self) -> ASTNamespace:
+ templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
+ name = self._parse_nested_name()
+ templatePrefix = self._check_template_consistency(name, templatePrefix,
+ fullSpecShorthand=False)
+ res = ASTNamespace(name, templatePrefix)
+ res.objectType = 'namespace' # type: ignore[attr-defined]
+ return res
+
+ def parse_xref_object(self) -> tuple[ASTNamespace | ASTDeclaration, bool]:
+ pos = self.pos
+ try:
+ templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
+ name = self._parse_nested_name()
+ # if there are '()' left, just skip them
+ self.skip_ws()
+ self.skip_string('()')
+ self.assert_end()
+ templatePrefix = self._check_template_consistency(name, templatePrefix,
+ fullSpecShorthand=True)
+ res1 = ASTNamespace(name, templatePrefix)
+ res1.objectType = 'xref' # type: ignore[attr-defined]
+ return res1, True
+ except DefinitionError as e1:
+ try:
+ self.pos = pos
+ res2 = self.parse_declaration('function', 'function')
+ # if there are '()' left, just skip them
+ self.skip_ws()
+ self.skip_string('()')
+ self.assert_end()
+ return res2, False
+ except DefinitionError as e2:
+ errs = []
+ errs.append((e1, "If shorthand ref"))
+ errs.append((e2, "If full function ref"))
+ msg = "Error in cross-reference."
+ raise self._make_multi_error(errs, msg) from e2
+
+ def parse_expression(self) -> ASTExpression | ASTType:
+ pos = self.pos
+ try:
+ expr = self._parse_expression()
+ self.skip_ws()
+ self.assert_end()
+ return expr
+ except DefinitionError as exExpr:
+ self.pos = pos
+ try:
+ typ = self._parse_type(False)
+ self.skip_ws()
+ self.assert_end()
+ return typ
+ except DefinitionError as exType:
+ header = "Error when parsing (type) expression."
+ errs = []
+ errs.append((exExpr, "If expression"))
+ errs.append((exType, "If type"))
+ raise self._make_multi_error(errs, header) from exType
+
+
+def _make_phony_error_name() -> ASTNestedName:
+ nne = ASTNestedNameElement(ASTIdentifier("PhonyNameDueToError"), None)
+ return ASTNestedName([nne], [False], rooted=False)
+
+
+class CPPObject(ObjectDescription[ASTDeclaration]):
+ """Description of a C++ language object."""
+
+ doc_field_types: list[Field] = [
+ GroupedField('template parameter', label=_('Template Parameters'),
+ names=('tparam', 'template parameter'),
+ can_collapse=True),
+ ]
+
+ option_spec: OptionSpec = {
+ 'no-index-entry': directives.flag,
+ 'no-contents-entry': directives.flag,
+ 'no-typesetting': directives.flag,
+ 'noindexentry': directives.flag,
+ 'nocontentsentry': directives.flag,
+ 'tparam-line-spec': directives.flag,
+ 'single-line-parameter-list': directives.flag,
+ }
+
+ def _add_enumerator_to_parent(self, ast: ASTDeclaration) -> None:
+ assert ast.objectType == 'enumerator'
+ # find the parent, if it exists && is an enum
+ # && it's unscoped,
+ # then add the name to the parent scope
+ symbol = ast.symbol
+ assert symbol
+ assert symbol.identOrOp is not None
+ assert symbol.templateParams is None
+ assert symbol.templateArgs is None
+ parentSymbol = symbol.parent
+ assert parentSymbol
+ if parentSymbol.parent is None:
+ # TODO: we could warn, but it is somewhat equivalent to unscoped
+ # enums, without the enum
+ return # no parent
+ parentDecl = parentSymbol.declaration
+ if parentDecl is None:
+ # the parent is not explicitly declared
+ # TODO: we could warn, but it could be a style to just assume
+ # enumerator parents to be scoped
+ return
+ if parentDecl.objectType != 'enum':
+ # TODO: maybe issue a warning, enumerators in non-enums is weird,
+ # but it is somewhat equivalent to unscoped enums, without the enum
+ return
+ if parentDecl.directiveType != 'enum':
+ return
+
+ targetSymbol = parentSymbol.parent
+ s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False, recurseInAnon=True,
+ searchInSiblings=False)
+ if s is not None:
+ # something is already declared with that name
+ return
+ declClone = symbol.declaration.clone()
+ declClone.enumeratorScopedSymbol = symbol
+ Symbol(parent=targetSymbol, identOrOp=symbol.identOrOp,
+ templateParams=None, templateArgs=None,
+ declaration=declClone,
+ docname=self.env.docname, line=self.get_source_info()[1])
+
+ def add_target_and_index(self, ast: ASTDeclaration, sig: str,
+ signode: TextElement) -> None:
+ # general note: name must be lstrip(':')'ed, to remove "::"
+ ids = []
+ for i in range(1, _max_id + 1):
+ try:
+ id = ast.get_id(version=i)
+ ids.append(id)
+ except NoOldIdError:
+ assert i < _max_id
+ # let's keep the newest first
+ ids = list(reversed(ids))
+ newestId = ids[0]
+ assert newestId # shouldn't be None
+ if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId):
+ logger.warning('Index id generation for C++ object "%s" failed, please '
+ 'report as bug (id=%s).', ast, newestId,
+ location=self.get_location())
+
+ name = ast.symbol.get_full_nested_name().get_display_string().lstrip(':')
+ # Add index entry, but not if it's a declaration inside a concept
+ isInConcept = False
+ s = ast.symbol.parent
+ while s is not None:
+ decl = s.declaration
+ s = s.parent
+ if decl is None:
+ continue
+ if decl.objectType == 'concept':
+ isInConcept = True
+ break
+ if not isInConcept and 'no-index-entry' not in self.options:
+ strippedName = name
+ for prefix in self.env.config.cpp_index_common_prefix:
+ if name.startswith(prefix):
+ strippedName = strippedName[len(prefix):]
+ break
+ indexText = self.get_index_text(strippedName)
+ self.indexnode['entries'].append(('single', indexText, newestId, '', None))
+
+ if newestId not in self.state.document.ids:
+ # if the name is not unique, the first one will win
+ names = self.env.domaindata['cpp']['names']
+ if name not in names:
+ names[name] = ast.symbol.docname
+ # always add the newest id
+ assert newestId
+ signode['ids'].append(newestId)
+ # only add compatibility ids when there are no conflicts
+ for id in ids[1:]:
+ if not id: # is None when the element didn't exist in that version
+ continue
+ if id not in self.state.document.ids:
+ signode['ids'].append(id)
+ self.state.document.note_explicit_target(signode)
+
+ @property
+ def object_type(self) -> str:
+ raise NotImplementedError
+
+ @property
+ def display_object_type(self) -> str:
+ return self.object_type
+
+ def get_index_text(self, name: str) -> str:
+ return _('%s (C++ %s)') % (name, self.display_object_type)
+
+ def parse_definition(self, parser: DefinitionParser) -> ASTDeclaration:
+ return parser.parse_declaration(self.object_type, self.objtype)
+
+ def describe_signature(self, signode: desc_signature,
+ ast: ASTDeclaration, options: dict) -> None:
+ ast.describe_signature(signode, 'lastIsName', self.env, options)
+
+ def run(self) -> list[Node]:
+ env = self.state.document.settings.env # from ObjectDescription.run
+ if 'cpp:parent_symbol' not in env.temp_data:
+ root = env.domaindata['cpp']['root_symbol']
+ env.temp_data['cpp:parent_symbol'] = root
+ env.ref_context['cpp:parent_key'] = root.get_lookup_key()
+
+ # The lookup keys assume that no nested scopes exists inside overloaded functions.
+ # (see also #5191)
+ # Example:
+ # .. cpp:function:: void f(int)
+ # .. cpp:function:: void f(double)
+ #
+ # .. cpp:function:: void g()
+ #
+ # :cpp:any:`boom`
+ #
+ # So we disallow any signatures inside functions.
+ parentSymbol = env.temp_data['cpp:parent_symbol']
+ parentDecl = parentSymbol.declaration
+ if parentDecl is not None and parentDecl.objectType == 'function':
+ msg = ("C++ declarations inside functions are not supported. "
+ f"Parent function: {parentSymbol.get_full_nested_name()}\n"
+ f"Directive name: {self.name}\nDirective arg: {self.arguments[0]}")
+ logger.warning(msg, location=self.get_location())
+ name = _make_phony_error_name()
+ symbol = parentSymbol.add_name(name)
+ env.temp_data['cpp:last_symbol'] = symbol
+ return []
+ # When multiple declarations are made in the same directive
+ # they need to know about each other to provide symbol lookup for function parameters.
+ # We use last_symbol to store the latest added declaration in a directive.
+ env.temp_data['cpp:last_symbol'] = None
+ return super().run()
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> ASTDeclaration:
+ parentSymbol: Symbol = self.env.temp_data['cpp:parent_symbol']
+
+ max_len = (self.env.config.cpp_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+ signode['multi_line_parameter_list'] = (
+ 'single-line-parameter-list' not in self.options
+ and (len(sig) > max_len > 0)
+ )
+
+ parser = DefinitionParser(sig, location=signode, config=self.env.config)
+ try:
+ ast = self.parse_definition(parser)
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=signode)
+ # It is easier to assume some phony name than handling the error in
+ # the possibly inner declarations.
+ name = _make_phony_error_name()
+ symbol = parentSymbol.add_name(name)
+ self.env.temp_data['cpp:last_symbol'] = symbol
+ raise ValueError from e
+
+ try:
+ 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
+ symbol.siblingAbove = self.env.temp_data['cpp:last_symbol']
+ if symbol.siblingAbove is not None:
+ assert symbol.siblingAbove.siblingBelow is None
+ symbol.siblingAbove.siblingBelow = symbol
+ self.env.temp_data['cpp:last_symbol'] = symbol
+ except _DuplicateSymbolError as e:
+ # 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 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':
+ self._add_enumerator_to_parent(ast)
+
+ # note: handle_signature may be called multiple time per directive,
+ # if it has multiple signatures, so don't mess with the original options.
+ options = dict(self.options)
+ options['tparam-line-spec'] = 'tparam-line-spec' in self.options
+ self.describe_signature(signode, ast, options)
+ return ast
+
+ def before_content(self) -> None:
+ lastSymbol: Symbol = self.env.temp_data['cpp:last_symbol']
+ assert lastSymbol
+ self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol']
+ self.oldParentKey: LookupKey = self.env.ref_context['cpp:parent_key']
+ self.env.temp_data['cpp:parent_symbol'] = lastSymbol
+ self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key()
+ self.env.temp_data['cpp:domain_name'] = (
+ *self.env.temp_data.get('cpp:domain_name', ()),
+ lastSymbol.identOrOp._stringify(str),
+ )
+
+ def after_content(self) -> None:
+ self.env.temp_data['cpp:parent_symbol'] = self.oldParentSymbol
+ self.env.ref_context['cpp:parent_key'] = self.oldParentKey
+ self.env.temp_data['cpp:domain_name'] = self.env.temp_data['cpp:domain_name'][:-1]
+
+ def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
+ return tuple(s.identOrOp._stringify(str) for s in
+ self.env.temp_data['cpp:last_symbol'].get_full_nested_name().names)
+
+ def _toc_entry_name(self, sig_node: desc_signature) -> str:
+ if not sig_node.get('_toc_parts'):
+ return ''
+
+ config = self.env.app.config
+ objtype = sig_node.parent.get('objtype')
+ if config.add_function_parentheses and objtype in {'function', 'method'}:
+ parens = '()'
+ else:
+ parens = ''
+ *parents, name = sig_node['_toc_parts']
+ if config.toc_object_entries_show_parents == 'domain':
+ return '::'.join((*self.env.temp_data.get('cpp:domain_name', ()), name + parens))
+ if config.toc_object_entries_show_parents == 'hide':
+ return name + parens
+ if config.toc_object_entries_show_parents == 'all':
+ return '::'.join(parents + [name + parens])
+ return ''
+
+
+class CPPTypeObject(CPPObject):
+ object_type = 'type'
+
+
+class CPPConceptObject(CPPObject):
+ object_type = 'concept'
+
+
+class CPPMemberObject(CPPObject):
+ object_type = 'member'
+
+
+class CPPFunctionObject(CPPObject):
+ object_type = 'function'
+
+ doc_field_types = CPPObject.doc_field_types + [
+ GroupedField('parameter', label=_('Parameters'),
+ names=('param', 'parameter', 'arg', 'argument'),
+ can_collapse=True),
+ GroupedField('exceptions', label=_('Throws'), rolename='expr',
+ names=('throws', 'throw', 'exception'),
+ can_collapse=True),
+ GroupedField('retval', label=_('Return values'),
+ names=('retvals', 'retval'),
+ can_collapse=True),
+ Field('returnvalue', label=_('Returns'), has_arg=False,
+ names=('returns', 'return')),
+ ]
+
+
+class CPPClassObject(CPPObject):
+ object_type = 'class'
+
+ @property
+ def display_object_type(self) -> str:
+ # the distinction between class and struct is only cosmetic
+ assert self.objtype in ('class', 'struct')
+ return self.objtype
+
+
+class CPPUnionObject(CPPObject):
+ object_type = 'union'
+
+
+class CPPEnumObject(CPPObject):
+ object_type = 'enum'
+
+
+class CPPEnumeratorObject(CPPObject):
+ object_type = 'enumerator'
+
+
+class CPPNamespaceObject(SphinxDirective):
+ """
+ This directive is just to tell Sphinx that we're documenting stuff in
+ namespace foo.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec: OptionSpec = {}
+
+ def run(self) -> list[Node]:
+ rootSymbol = self.env.domaindata['cpp']['root_symbol']
+ if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
+ symbol = rootSymbol
+ stack: list[Symbol] = []
+ else:
+ parser = DefinitionParser(self.arguments[0],
+ location=self.get_location(),
+ config=self.config)
+ try:
+ ast = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_location())
+ name = _make_phony_error_name()
+ ast = ASTNamespace(name, None)
+ symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix)
+ stack = [symbol]
+ self.env.temp_data['cpp:parent_symbol'] = symbol
+ self.env.temp_data['cpp:namespace_stack'] = stack
+ self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CPPNamespacePushObject(SphinxDirective):
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec: OptionSpec = {}
+
+ def run(self) -> list[Node]:
+ if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
+ return []
+ parser = DefinitionParser(self.arguments[0],
+ location=self.get_location(),
+ config=self.config)
+ try:
+ ast = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_location())
+ name = _make_phony_error_name()
+ ast = ASTNamespace(name, None)
+ oldParent = self.env.temp_data.get('cpp:parent_symbol', None)
+ if not oldParent:
+ oldParent = self.env.domaindata['cpp']['root_symbol']
+ symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix)
+ stack = self.env.temp_data.get('cpp:namespace_stack', [])
+ stack.append(symbol)
+ self.env.temp_data['cpp:parent_symbol'] = symbol
+ self.env.temp_data['cpp:namespace_stack'] = stack
+ self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CPPNamespacePopObject(SphinxDirective):
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec: OptionSpec = {}
+
+ def run(self) -> list[Node]:
+ stack = self.env.temp_data.get('cpp:namespace_stack', None)
+ if not stack or len(stack) == 0:
+ logger.warning("C++ namespace pop on empty stack. Defaulting to global scope.",
+ location=self.get_location())
+ stack = []
+ else:
+ stack.pop()
+ if len(stack) > 0:
+ symbol = stack[-1]
+ else:
+ symbol = self.env.domaindata['cpp']['root_symbol']
+ self.env.temp_data['cpp:parent_symbol'] = symbol
+ self.env.temp_data['cpp:namespace_stack'] = stack
+ self.env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class AliasNode(nodes.Element):
+ def __init__(self, sig: str, aliasOptions: dict,
+ env: BuildEnvironment | None = None,
+ parentKey: LookupKey | None = None) -> None:
+ super().__init__()
+ self.sig = sig
+ self.aliasOptions = aliasOptions
+ if env is not None:
+ if 'cpp:parent_symbol' not in env.temp_data:
+ root = env.domaindata['cpp']['root_symbol']
+ env.temp_data['cpp:parent_symbol'] = root
+ env.ref_context['cpp:parent_key'] = root.get_lookup_key()
+ self.parentKey = env.ref_context['cpp:parent_key']
+ else:
+ assert parentKey is not None
+ self.parentKey = parentKey
+
+ def copy(self) -> AliasNode:
+ return self.__class__(self.sig, self.aliasOptions,
+ env=None, parentKey=self.parentKey)
+
+
+class AliasTransform(SphinxTransform):
+ default_priority = ReferencesResolver.default_priority - 1
+
+ def _render_symbol(self, s: Symbol, maxdepth: int, skipThis: bool,
+ aliasOptions: dict, renderOptions: dict,
+ document: Any) -> list[Node]:
+ if maxdepth == 0:
+ recurse = True
+ elif maxdepth == 1:
+ recurse = False
+ else:
+ maxdepth -= 1
+ recurse = True
+
+ nodes: list[Node] = []
+ if not skipThis:
+ signode = addnodes.desc_signature('', '')
+ nodes.append(signode)
+ s.declaration.describe_signature(signode, 'markName', self.env, renderOptions)
+
+ if recurse:
+ if skipThis:
+ childContainer: list[Node] | addnodes.desc = nodes
+ else:
+ content = addnodes.desc_content()
+ desc = addnodes.desc()
+ content.append(desc)
+ desc.document = document
+ desc['domain'] = 'cpp'
+ # 'desctype' is a backwards compatible attribute
+ desc['objtype'] = desc['desctype'] = 'alias'
+ desc['no-index'] = True
+ childContainer = desc
+
+ for sChild in s._children:
+ if sChild.declaration is None:
+ continue
+ if sChild.declaration.objectType in ("templateParam", "functionParam"):
+ continue
+ childNodes = self._render_symbol(
+ sChild, maxdepth=maxdepth, skipThis=False,
+ aliasOptions=aliasOptions, renderOptions=renderOptions,
+ document=document)
+ childContainer.extend(childNodes)
+
+ if not skipThis and len(desc.children) != 0:
+ nodes.append(content)
+ return nodes
+
+ def apply(self, **kwargs: Any) -> None:
+ for node in self.document.findall(AliasNode):
+ sig = node.sig
+ parentKey = node.parentKey
+ try:
+ parser = DefinitionParser(sig, location=node,
+ config=self.env.config)
+ ast, isShorthand = parser.parse_xref_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=node)
+ ast, isShorthand = None, None
+
+ if ast is None:
+ # could not be parsed, so stop here
+ signode = addnodes.desc_signature(sig, '')
+ signode.clear()
+ signode += addnodes.desc_name(sig, sig)
+ node.replace_self(signode)
+ continue
+
+ rootSymbol: Symbol = self.env.domains['cpp'].data['root_symbol']
+ parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey)
+ if not parentSymbol:
+ logger.debug("Target: %s", sig)
+ logger.debug("ParentKey: %s", parentKey)
+ logger.debug(rootSymbol.dump(1))
+ assert parentSymbol # should be there
+
+ symbols: list[Symbol] = []
+ if isShorthand:
+ assert isinstance(ast, ASTNamespace)
+ ns = ast
+ name = ns.nestedName
+ if ns.templatePrefix:
+ templateDecls = ns.templatePrefix.templates
+ else:
+ templateDecls = []
+ symbols, failReason = parentSymbol.find_name(
+ nestedName=name,
+ templateDecls=templateDecls,
+ typ='any',
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True,
+ searchInSiblings=False)
+ if symbols is None:
+ symbols = []
+ else:
+ assert isinstance(ast, ASTDeclaration)
+ decl = ast
+ name = decl.name
+ s = parentSymbol.find_declaration(decl, 'any',
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True)
+ if s is not None:
+ symbols.append(s)
+
+ symbols = [s for s in symbols if s.declaration is not None]
+
+ if len(symbols) == 0:
+ signode = addnodes.desc_signature(sig, '')
+ node.append(signode)
+ signode.clear()
+ signode += addnodes.desc_name(sig, sig)
+
+ logger.warning("Can not find C++ declaration for alias '%s'." % ast,
+ location=node)
+ node.replace_self(signode)
+ else:
+ nodes = []
+ renderOptions = {
+ 'tparam-line-spec': False,
+ }
+ for s in symbols:
+ assert s.declaration is not None
+ res = self._render_symbol(
+ s, maxdepth=node.aliasOptions['maxdepth'],
+ skipThis=node.aliasOptions['noroot'],
+ aliasOptions=node.aliasOptions,
+ renderOptions=renderOptions,
+ document=node.document)
+ nodes.extend(res)
+ node.replace_self(nodes)
+
+
+class CPPAliasObject(ObjectDescription):
+ option_spec: OptionSpec = {
+ 'maxdepth': directives.nonnegative_int,
+ 'noroot': directives.flag,
+ }
+
+ def run(self) -> list[Node]:
+ """
+ On purpose this doesn't call the ObjectDescription version, but is based on it.
+ Each alias signature may expand into multiple real signatures (an overload set).
+ The code is therefore based on the ObjectDescription version.
+ """
+ if ':' in self.name:
+ self.domain, self.objtype = self.name.split(':', 1)
+ else:
+ self.domain, self.objtype = '', self.name
+
+ node = addnodes.desc()
+ node.document = self.state.document
+ node['domain'] = self.domain
+ # 'desctype' is a backwards compatible attribute
+ node['objtype'] = node['desctype'] = self.objtype
+
+ self.names: list[str] = []
+ aliasOptions = {
+ 'maxdepth': self.options.get('maxdepth', 1),
+ 'noroot': 'noroot' in self.options,
+ }
+ if aliasOptions['noroot'] and aliasOptions['maxdepth'] == 1:
+ logger.warning("Error in C++ alias declaration."
+ " Requested 'noroot' but 'maxdepth' 1."
+ " When skipping the root declaration,"
+ " need 'maxdepth' 0 for infinite or at least 2.",
+ location=self.get_location())
+ signatures = self.get_signatures()
+ for sig in signatures:
+ node.append(AliasNode(sig, aliasOptions, env=self.env))
+
+ contentnode = addnodes.desc_content()
+ node.append(contentnode)
+ self.before_content()
+ self.state.nested_parse(self.content, self.content_offset, contentnode)
+ self.env.temp_data['object'] = None
+ self.after_content()
+ return [node]
+
+
+class CPPXRefRole(XRefRole):
+ def process_link(self, env: BuildEnvironment, refnode: Element, has_explicit_title: bool,
+ title: str, target: str) -> tuple[str, str]:
+ refnode.attributes.update(env.ref_context)
+
+ if not has_explicit_title:
+ # major hax: replace anon names via simple string manipulation.
+ # Can this actually fail?
+ title = anon_identifier_re.sub("[anonymous]", str(title))
+
+ if refnode['reftype'] == 'any':
+ # Assume the removal part of fix_parens for :any: refs.
+ # The addition part is done with the reference is resolved.
+ if not has_explicit_title and title.endswith('()'):
+ title = title[:-2]
+ if target.endswith('()'):
+ target = target[:-2]
+ # TODO: should this really be here?
+ if not has_explicit_title:
+ target = target.lstrip('~') # only has a meaning for the title
+ # if the first character is a tilde, don't display the module/class
+ # parts of the contents
+ if title[:1] == '~':
+ title = title[1:]
+ dcolon = title.rfind('::')
+ if dcolon != -1:
+ title = title[dcolon + 2:]
+ return title, target
+
+
+class CPPExprRole(SphinxRole):
+ def __init__(self, asCode: bool) -> None:
+ super().__init__()
+ if asCode:
+ # render the expression as inline code
+ self.class_type = 'cpp-expr'
+ else:
+ # render the expression as inline text
+ self.class_type = 'cpp-texpr'
+
+ def run(self) -> tuple[list[Node], list[system_message]]:
+ text = self.text.replace('\n', ' ')
+ parser = DefinitionParser(text,
+ location=self.get_location(),
+ config=self.config)
+ # attempt to mimic XRefRole classes, except that...
+ try:
+ ast = parser.parse_expression()
+ except DefinitionError as ex:
+ logger.warning('Unparseable C++ expression: %r\n%s', text, ex,
+ location=self.get_location())
+ # see below
+ return [addnodes.desc_inline('cpp', text, text, classes=[self.class_type])], []
+ parentSymbol = self.env.temp_data.get('cpp:parent_symbol', None)
+ if parentSymbol is None:
+ parentSymbol = self.env.domaindata['cpp']['root_symbol']
+ # ...most if not all of these classes should really apply to the individual references,
+ # not the container node
+ signode = addnodes.desc_inline('cpp', classes=[self.class_type])
+ ast.describe_signature(signode, 'markType', self.env, parentSymbol)
+ return [signode], []
+
+
+class CPPDomain(Domain):
+ """C++ language domain.
+
+ There are two 'object type' attributes being used::
+
+ - Each object created from directives gets an assigned .objtype from ObjectDescription.run.
+ This is simply the directive name.
+ - Each declaration (see the distinction in the directives dict below) has a nested .ast of
+ type ASTDeclaration. That object has .objectType which corresponds to the keys in the
+ object_types dict below. They are the core different types of declarations in C++ that
+ one can document.
+ """
+ name = 'cpp'
+ label = 'C++'
+ object_types = {
+ 'class': ObjType(_('class'), 'class', 'struct', 'identifier', 'type'),
+ 'union': ObjType(_('union'), 'union', 'identifier', 'type'),
+ 'function': ObjType(_('function'), 'func', 'identifier', 'type'),
+ 'member': ObjType(_('member'), 'member', 'var', 'identifier'),
+ 'type': ObjType(_('type'), 'identifier', 'type'),
+ 'concept': ObjType(_('concept'), 'concept', 'identifier'),
+ 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'),
+ 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'),
+ # generated object types
+ 'functionParam': ObjType(_('function parameter'), 'identifier', 'member', 'var'), # noqa: E501
+ 'templateParam': ObjType(_('template parameter'),
+ 'identifier', 'class', 'struct', 'union', 'member', 'var', 'type'), # noqa: E501
+ }
+
+ directives = {
+ # declarations
+ 'class': CPPClassObject,
+ 'struct': CPPClassObject,
+ 'union': CPPUnionObject,
+ 'function': CPPFunctionObject,
+ 'member': CPPMemberObject,
+ 'var': CPPMemberObject,
+ 'type': CPPTypeObject,
+ 'concept': CPPConceptObject,
+ 'enum': CPPEnumObject,
+ 'enum-struct': CPPEnumObject,
+ 'enum-class': CPPEnumObject,
+ 'enumerator': CPPEnumeratorObject,
+ # scope control
+ 'namespace': CPPNamespaceObject,
+ 'namespace-push': CPPNamespacePushObject,
+ 'namespace-pop': CPPNamespacePopObject,
+ # other
+ 'alias': CPPAliasObject,
+ }
+ roles = {
+ 'any': CPPXRefRole(),
+ 'class': CPPXRefRole(),
+ 'struct': CPPXRefRole(),
+ 'union': CPPXRefRole(),
+ 'func': CPPXRefRole(fix_parens=True),
+ 'member': CPPXRefRole(),
+ 'var': CPPXRefRole(),
+ 'type': CPPXRefRole(),
+ 'concept': CPPXRefRole(),
+ 'enum': CPPXRefRole(),
+ 'enumerator': CPPXRefRole(),
+ 'expr': CPPExprRole(asCode=True),
+ 'texpr': CPPExprRole(asCode=False),
+ }
+ initial_data = {
+ 'root_symbol': Symbol(None, None, None, None, None, None, None),
+ 'names': {}, # full name for indexing -> docname
+ }
+
+ def clear_doc(self, docname: str) -> None:
+ if Symbol.debug_show_tree:
+ logger.debug("clear_doc: %s", docname)
+ logger.debug("\tbefore:")
+ logger.debug(self.data['root_symbol'].dump(1))
+ logger.debug("\tbefore end")
+
+ rootSymbol = self.data['root_symbol']
+ rootSymbol.clear_doc(docname)
+
+ if Symbol.debug_show_tree:
+ logger.debug("\tafter:")
+ logger.debug(self.data['root_symbol'].dump(1))
+ logger.debug("\tafter end")
+ logger.debug("clear_doc end: %s", docname)
+ for name, nDocname in list(self.data['names'].items()):
+ if nDocname == docname:
+ del self.data['names'][name]
+
+ def process_doc(self, env: BuildEnvironment, docname: str,
+ document: nodes.document) -> None:
+ if Symbol.debug_show_tree:
+ logger.debug("process_doc: %s", docname)
+ logger.debug(self.data['root_symbol'].dump(0))
+ logger.debug("process_doc end: %s", docname)
+
+ def process_field_xref(self, pnode: pending_xref) -> None:
+ pnode.attributes.update(self.env.ref_context)
+
+ def merge_domaindata(self, docnames: list[str], otherdata: dict) -> None:
+ if Symbol.debug_show_tree:
+ logger.debug("merge_domaindata:")
+ logger.debug("\tself:")
+ logger.debug(self.data['root_symbol'].dump(1))
+ logger.debug("\tself end")
+ logger.debug("\tother:")
+ logger.debug(otherdata['root_symbol'].dump(1))
+ logger.debug("\tother end")
+
+ self.data['root_symbol'].merge_with(otherdata['root_symbol'],
+ docnames, self.env)
+ ourNames = self.data['names']
+ for name, docname in otherdata['names'].items():
+ if docname in docnames:
+ if name not in ourNames:
+ ourNames[name] = docname
+ # no need to warn on duplicates, the symbol merge already does that
+ if Symbol.debug_show_tree:
+ logger.debug("\tresult:")
+ logger.debug(self.data['root_symbol'].dump(1))
+ logger.debug("\tresult end")
+ logger.debug("merge_domaindata end")
+
+ def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
+ typ: str, target: str, node: pending_xref,
+ contnode: Element) -> tuple[Element | None, str | None]:
+ # add parens again for those that could be functions
+ if typ in ('any', 'func'):
+ target += '()'
+ parser = DefinitionParser(target, location=node, config=env.config)
+ try:
+ ast, isShorthand = parser.parse_xref_object()
+ except DefinitionError as e:
+ # as arg to stop flake8 from complaining
+ def findWarning(e: Exception) -> tuple[str, Exception]:
+ if typ != 'any' and typ != 'func':
+ return target, e
+ # hax on top of the paren hax to try to get correct errors
+ parser2 = DefinitionParser(target[:-2],
+ location=node,
+ config=env.config)
+ try:
+ parser2.parse_xref_object()
+ except DefinitionError as e2:
+ return target[:-2], e2
+ # strange, that we don't get the error now, use the original
+ return target, e
+ t, ex = findWarning(e)
+ logger.warning('Unparseable C++ cross-reference: %r\n%s', t, ex,
+ location=node)
+ return None, None
+ parentKey: LookupKey = node.get("cpp:parent_key", None)
+ rootSymbol = self.data['root_symbol']
+ if parentKey:
+ parentSymbol: Symbol = rootSymbol.direct_lookup(parentKey)
+ if not parentSymbol:
+ logger.debug("Target: %s", target)
+ logger.debug("ParentKey: %s", parentKey.data)
+ logger.debug(rootSymbol.dump(1))
+ assert parentSymbol # should be there
+ else:
+ parentSymbol = rootSymbol
+
+ if isShorthand:
+ assert isinstance(ast, ASTNamespace)
+ ns = ast
+ name = ns.nestedName
+ if ns.templatePrefix:
+ templateDecls = ns.templatePrefix.templates
+ else:
+ templateDecls = []
+ # let's be conservative with the sibling lookup for now
+ searchInSiblings = (not name.rooted) and len(name.names) == 1
+ symbols, failReason = parentSymbol.find_name(
+ name, templateDecls, typ,
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True,
+ searchInSiblings=searchInSiblings)
+ if symbols is None:
+ if typ == 'identifier':
+ if failReason == 'templateParamInQualified':
+ # this is an xref we created as part of a signature,
+ # so don't warn for names nested in template parameters
+ raise NoUri(str(name), typ)
+ s = None
+ else:
+ # just refer to the arbitrarily first symbol
+ s = symbols[0]
+ else:
+ assert isinstance(ast, ASTDeclaration)
+ decl = ast
+ name = decl.name
+ s = parentSymbol.find_declaration(decl, typ,
+ templateShorthand=True,
+ matchSelf=True, recurseInAnon=True)
+ if s is None or s.declaration is None:
+ txtName = str(name)
+ if txtName.startswith('std::') or txtName == 'std':
+ raise NoUri(txtName, typ)
+ return None, None
+
+ if typ.startswith('cpp:'):
+ typ = typ[4:]
+ declTyp = s.declaration.objectType
+
+ def checkType() -> bool:
+ if typ == 'any':
+ return True
+ objtypes = self.objtypes_for_role(typ)
+ if objtypes:
+ return declTyp in objtypes
+ logger.debug(f"Type is {typ}, declaration type is {declTyp}") # NoQA: G004
+ raise AssertionError
+ if not checkType():
+ logger.warning("cpp:%s targets a %s (%s).",
+ typ, s.declaration.objectType,
+ s.get_full_nested_name(),
+ location=node)
+
+ declaration = s.declaration
+ if isShorthand:
+ fullNestedName = s.get_full_nested_name()
+ displayName = fullNestedName.get_display_string().lstrip(':')
+ else:
+ displayName = decl.get_display_string()
+ docname = s.docname
+ assert docname
+
+ # the non-identifier refs are cross-references, which should be processed:
+ # - fix parenthesis due to operator() and add_function_parentheses
+ if typ != "identifier":
+ title = contnode.pop(0).astext()
+ # If it's operator(), we need to add '()' if explicit function parens
+ # are requested. Then the Sphinx machinery will add another pair.
+ # Also, if it's an 'any' ref that resolves to a function, we need to add
+ # parens as well.
+ # However, if it's a non-shorthand function ref, for a function that
+ # takes no arguments, then we may need to add parens again as well.
+ addParen = 0
+ if not node.get('refexplicit', False) and declaration.objectType == 'function':
+ if isShorthand:
+ # this is just the normal haxing for 'any' roles
+ if env.config.add_function_parentheses and typ == 'any':
+ addParen += 1
+ # and now this stuff for operator()
+ if (env.config.add_function_parentheses and typ == 'func' and
+ title.endswith('operator()')):
+ addParen += 1
+ if (typ in ('any', 'func') and
+ title.endswith('operator') and
+ displayName.endswith('operator()')):
+ addParen += 1
+ else:
+ # our job here is to essentially nullify add_function_parentheses
+ if env.config.add_function_parentheses:
+ if typ == 'any' and displayName.endswith('()'):
+ addParen += 1
+ elif typ == 'func':
+ if title.endswith('()') and not displayName.endswith('()'):
+ title = title[:-2]
+ else:
+ if displayName.endswith('()'):
+ addParen += 1
+ if addParen > 0:
+ title += '()' * addParen
+ # and reconstruct the title again
+ contnode += nodes.Text(title)
+ res = make_refnode(builder, fromdocname, docname,
+ declaration.get_newest_id(), contnode, displayName,
+ ), declaration.objectType
+ return res
+
+ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
+ typ: str, target: str, node: pending_xref, contnode: Element,
+ ) -> Element | None:
+ return self._resolve_xref_inner(env, fromdocname, builder, typ,
+ target, node, contnode)[0]
+
+ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
+ target: str, node: pending_xref, contnode: Element,
+ ) -> list[tuple[str, Element]]:
+ with logging.suppress_logging():
+ retnode, objtype = self._resolve_xref_inner(env, fromdocname, builder,
+ 'any', target, node, contnode)
+ if retnode:
+ if objtype == 'templateParam':
+ return [('cpp:templateParam', retnode)]
+ else:
+ return [('cpp:' + self.role_for_objtype(objtype), retnode)]
+ return []
+
+ def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
+ rootSymbol = self.data['root_symbol']
+ for symbol in rootSymbol.get_all_symbols():
+ if symbol.declaration is None:
+ continue
+ assert symbol.docname
+ fullNestedName = symbol.get_full_nested_name()
+ name = str(fullNestedName).lstrip(':')
+ dispname = fullNestedName.get_display_string().lstrip(':')
+ objectType = symbol.declaration.objectType
+ docname = symbol.docname
+ newestId = symbol.declaration.get_newest_id()
+ yield (name, dispname, objectType, docname, newestId, 1)
+
+ def get_full_qualified_name(self, node: Element) -> str:
+ target = node.get('reftarget', None)
+ if target is None:
+ return None
+ parentKey: LookupKey = node.get("cpp:parent_key", None)
+ if parentKey is None or len(parentKey.data) <= 0:
+ return None
+
+ rootSymbol = self.data['root_symbol']
+ parentSymbol = rootSymbol.direct_lookup(parentKey)
+ parentName = parentSymbol.get_full_nested_name()
+ return '::'.join([str(parentName), target])
+
+
+def setup(app: Sphinx) -> dict[str, Any]:
+ app.add_domain(CPPDomain)
+ app.add_config_value("cpp_index_common_prefix", [], 'env')
+ app.add_config_value("cpp_id_attributes", [], 'env')
+ app.add_config_value("cpp_paren_attributes", [], 'env')
+ app.add_config_value("cpp_maximum_signature_line_length", None, 'env', types={int, None})
+ app.add_post_transform(AliasTransform)
+
+ # debug stuff
+ app.add_config_value("cpp_debug_lookup", False, '')
+ app.add_config_value("cpp_debug_show_tree", False, '')
+
+ def initStuff(app):
+ Symbol.debug_lookup = app.config.cpp_debug_lookup
+ Symbol.debug_show_tree = app.config.cpp_debug_show_tree
+ app.config.cpp_index_common_prefix.sort(reverse=True)
+ app.connect("builder-inited", initStuff)
+
+ return {
+ 'version': 'builtin',
+ 'env_version': 9,
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }