summaryrefslogtreecommitdiffstats
path: root/sphinx/domains
diff options
context:
space:
mode:
Diffstat (limited to 'sphinx/domains')
-rw-r--r--sphinx/domains/__init__.py4
-rw-r--r--sphinx/domains/c.py3906
-rw-r--r--sphinx/domains/c/__init__.py849
-rw-r--r--sphinx/domains/c/_ast.py1421
-rw-r--r--sphinx/domains/c/_ids.py65
-rw-r--r--sphinx/domains/c/_parser.py1048
-rw-r--r--sphinx/domains/c/_symbol.py700
-rw-r--r--sphinx/domains/changeset.py18
-rw-r--r--sphinx/domains/citation.py7
-rw-r--r--sphinx/domains/cpp.py8233
-rw-r--r--sphinx/domains/cpp/__init__.py1181
-rw-r--r--sphinx/domains/cpp/_ast.py3635
-rw-r--r--sphinx/domains/cpp/_ids.py537
-rw-r--r--sphinx/domains/cpp/_parser.py2117
-rw-r--r--sphinx/domains/cpp/_symbol.py1092
-rw-r--r--sphinx/domains/index.py10
-rw-r--r--sphinx/domains/javascript.py29
-rw-r--r--sphinx/domains/math.py13
-rw-r--r--sphinx/domains/python.py1769
-rw-r--r--sphinx/domains/python/__init__.py904
-rw-r--r--sphinx/domains/python/_annotations.py507
-rw-r--r--sphinx/domains/python/_object.py426
-rw-r--r--sphinx/domains/rst.py19
-rw-r--r--sphinx/domains/std/__init__.py (renamed from sphinx/domains/std.py)35
24 files changed, 14562 insertions, 13963 deletions
diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py
index 7c11220..6c2dc7b 100644
--- a/sphinx/domains/__init__.py
+++ b/sphinx/domains/__init__.py
@@ -93,7 +93,7 @@ class Index(ABC):
shortname: str | None = None
def __init__(self, domain: Domain) -> None:
- if self.name is None or self.localname is None:
+ if not self.name or self.localname is None:
raise SphinxError('Index subclass %s has no valid name or localname'
% self.__class__.__name__)
self.domain = domain
@@ -296,7 +296,7 @@ class Domain:
"""Remove traces of a document in the domain-specific inventories."""
pass
- def merge_domaindata(self, docnames: list[str], otherdata: dict) -> None:
+ def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
"""Merge in data regarding *docnames* from a different domaindata
inventory (coming from a subprocess in parallel builds).
"""
diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py
deleted file mode 100644
index 9444c6b..0000000
--- a/sphinx/domains/c.py
+++ /dev/null
@@ -1,3906 +0,0 @@
-"""The C language domain."""
-
-from __future__ import annotations
-
-import re
-from typing import TYPE_CHECKING, Any, Callable, TypeVar, Union, cast
-
-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.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, TypedField
-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 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')
-
-DeclarationType = Union[
- "ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator",
- "ASTType", "ASTTypeWithInit", "ASTMacro",
-]
-
-# https://en.cppreference.com/w/c/keyword
-_keywords = [
- 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double',
- 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long',
- 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct',
- 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
- '_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex',
- '_Decimal32', '_Decimal64', '_Decimal128',
- '_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local',
-]
-# These are only keyword'y when the corresponding headers are included.
-# They are used as default value for c_extra_keywords.
-_macroKeywords = [
- 'alignas', 'alignof', 'bool', 'complex', 'imaginary', 'noreturn', 'static_assert',
- 'thread_local',
-]
-
-# these are ordered by precedence
-_expression_bin_ops = [
- ['||', 'or'],
- ['&&', 'and'],
- ['|', 'bitor'],
- ['^', 'xor'],
- ['&', 'bitand'],
- ['==', '!=', 'not_eq'],
- ['<=', '>=', '<', '>'],
- ['<<', '>>'],
- ['+', '-'],
- ['*', '/', '%'],
- ['.*', '->*'],
-]
-_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
-_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
- ">>=", "<<=", "&=", "and_eq", "^=", "xor_eq", "|=", "or_eq"]
-
-_max_id = 1
-_id_prefix = [None, 'c.', 'Cv2.']
-# Ids are used in lookup keys which are used across pickled files,
-# so when _max_id changes, make sure to update the ENV_VERSION.
-
-_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
- r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
-
-# bool, complex, and imaginary are macro "keywords", so they are handled separately
-_simple_type_specifiers_re = re.compile(r"""
- \b(
- void|_Bool
- |signed|unsigned
- |short|long
- |char
- |int
- |__uint128|__int128
- |__int(8|16|32|64|128) # extension
- |float|double
- |_Decimal(32|64|128)
- |_Complex|_Imaginary
- |__float80|_Float64x|__float128|_Float128|__ibm128 # extension
- |__fp16 # extension
- |_Sat|_Fract|fract|_Accum|accum # extension
- )\b
-""", re.VERBOSE)
-
-
-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):
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- raise NotImplementedError(repr(self))
-
-
-# Names
-################################################################################
-
-class ASTIdentifier(ASTBaseBase):
- def __init__(self, identifier: str) -> None:
- assert identifier is not None
- assert len(identifier) != 0
- self.identifier = identifier
-
- def __eq__(self, other: Any) -> bool:
- return type(other) is ASTIdentifier and self.identifier == other.identifier
-
- def is_anon(self) -> bool:
- return self.identifier[0] == '@'
-
- # 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, symbol: Symbol) -> None:
- # note: slightly different signature of describe_signature due to the prefix
- 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
- pnode = addnodes.pending_xref('', refdomain='c',
- reftype='identifier',
- reftarget=targetText, modname=None,
- classname=None)
- pnode['c: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
- else:
- raise Exception('Unknown description mode: %s' % mode)
-
-
-class ASTNestedName(ASTBase):
- def __init__(self, names: list[ASTIdentifier], rooted: bool) -> None:
- assert len(names) > 0
- self.names = names
- self.rooted = rooted
-
- @property
- def name(self) -> ASTNestedName:
- return self
-
- def get_id(self, version: int) -> str:
- return '.'.join(str(n) for n in self.names)
-
- def _stringify(self, transform: StringifyTransform) -> str:
- res = '.'.join(transform(n) for n in self.names)
- if self.rooted:
- return '.' + res
- else:
- return 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('.')
- n = self.names[i]
- n.describe_signature(signode, mode, env, '', symbol)
- elif mode == 'param':
- assert not self.rooted, str(self)
- assert len(self.names) == 1
- self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol)
- elif mode in ('markType', 'lastIsName', 'markName'):
- # Each element should be a pending xref targeting the complete
- # prefix.
- 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.
- # TODO: also for C?
- # 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)):
- ident = names[i]
- if not first:
- dest += addnodes.desc_sig_punctuation('.', '.')
- prefix += '.'
- first = False
- txt_ident = str(ident)
- if txt_ident != '':
- ident.describe_signature(dest, 'markType', env, prefix, symbol)
- prefix += txt_ident
- if mode == 'lastIsName':
- if len(self.names) > 1:
- dest += addnodes.desc_sig_punctuation('.', '.')
- signode += dest
- self.names[-1].describe_signature(signode, mode, env, '', symbol)
- else:
- raise Exception('Unknown description mode: %s' % mode)
-
-
-################################################################################
-# Expressions
-################################################################################
-
-class ASTExpression(ASTBase):
- pass
-
-
-# Primary expressions
-################################################################################
-
-class ASTLiteral(ASTExpression):
- pass
-
-
-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 describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- txt = str(self)
- signode += addnodes.desc_sig_keyword(txt, txt)
-
-
-class ASTNumberLiteral(ASTLiteral):
- def __init__(self, data: str) -> None:
- self.data = data
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return self.data
-
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- txt = str(self)
- signode += addnodes.desc_sig_literal_number(txt, txt)
-
-
-class ASTCharLiteral(ASTLiteral):
- def __init__(self, prefix: str, data: str) -> None:
- self.prefix = prefix # may be None when no prefix
- self.data = data
- 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 describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- txt = str(self)
- signode += addnodes.desc_sig_literal_char(txt, txt)
-
-
-class ASTStringLiteral(ASTLiteral):
- def __init__(self, data: str) -> None:
- self.data = data
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return self.data
-
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- txt = str(self)
- signode += addnodes.desc_sig_literal_string(txt, txt)
-
-
-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)
-
-
-class ASTParenExpr(ASTExpression):
- def __init__(self, expr):
- 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(')', ')')
-
-
-# Postfix expressions
-################################################################################
-
-class ASTPostfixOp(ASTBase):
- pass
-
-
-class ASTPostfixCallExpr(ASTPostfixOp):
- def __init__(self, lst: ASTParenExprList | ASTBracedInitList) -> None:
- self.lst = lst
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return transform(self.lst)
-
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- self.lst.describe_signature(signode, mode, env, symbol)
-
-
-class ASTPostfixArray(ASTPostfixOp):
- def __init__(self, expr: ASTExpression) -> None:
- self.expr = expr
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return '[' + transform(self.expr) + ']'
-
- 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 ASTPostfixInc(ASTPostfixOp):
- def _stringify(self, transform: StringifyTransform) -> str:
- return '++'
-
- 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 describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- signode += addnodes.desc_sig_operator('--', '--')
-
-
-class ASTPostfixMemberOfPointer(ASTPostfixOp):
- def __init__(self, name):
- self.name = name
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return '->' + transform(self.name)
-
- 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 ASTPostfixExpr(ASTExpression):
- def __init__(self, prefix: ASTExpression, 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 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)
-
-
-# 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 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 ASTSizeofType(ASTExpression):
- def __init__(self, typ):
- self.typ = typ
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return "sizeof(" + transform(self.typ) + ")"
-
- 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 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 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(')', ')')
-
-
-# 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 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(ASTBase):
- 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 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 ASTAssignmentExpr(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 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 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
-################################################################################
-
-class ASTTrailingTypeSpec(ASTBase):
- pass
-
-
-class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
- def __init__(self, names: list[str]) -> None:
- assert len(names) != 0
- self.names = names
-
- def _stringify(self, transform: StringifyTransform) -> str:
- return ' '.join(self.names)
-
- 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 ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
- def __init__(self, prefix: str, nestedName: ASTNestedName) -> None:
- self.prefix = prefix
- self.nestedName = nestedName
-
- @property
- def name(self) -> ASTNestedName:
- return self.nestedName
-
- def _stringify(self, transform: StringifyTransform) -> str:
- res = []
- if self.prefix:
- res.append(self.prefix)
- res.append(' ')
- res.append(transform(self.nestedName))
- 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)
-
-
-class ASTFunctionParameter(ASTBase):
- def __init__(self, arg: ASTTypeWithInit | None, ellipsis: bool = False) -> None:
- self.arg = arg
- self.ellipsis = ellipsis
-
- def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
- # the anchor will be our parent
- return symbol.parent.declaration.get_id(version, prefixed=False)
-
- def _stringify(self, transform: StringifyTransform) -> str:
- if self.ellipsis:
- return '...'
- else:
- return transform(self.arg)
-
- def describe_signature(self, signode: Any, 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 ASTParameters(ASTBase):
- def __init__(self, args: list[ASTFunctionParameter], attrs: ASTAttributeList) -> None:
- self.args = args
- self.attrs = attrs
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- return 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 len(self.attrs) != 0:
- res.append(' ')
- res.append(transform(self.attrs))
- 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(')', ')')
-
- if len(self.attrs) != 0:
- signode += addnodes.desc_sig_space()
- self.attrs.describe_signature(signode)
-
-
-class ASTDeclSpecsSimple(ASTBaseBase):
- def __init__(self, storage: str, threadLocal: str, inline: bool,
- restrict: bool, volatile: bool, const: bool, attrs: ASTAttributeList) -> None:
- self.storage = storage
- self.threadLocal = threadLocal
- self.inline = inline
- self.restrict = restrict
- self.volatile = volatile
- self.const = const
- 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.volatile or other.volatile,
- self.const or other.const,
- self.restrict or other.restrict,
- 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(self.threadLocal)
- if self.inline:
- res.append('inline')
- if self.restrict:
- res.append('restrict')
- if self.volatile:
- res.append('volatile')
- if self.const:
- res.append('const')
- return ' '.join(res)
-
- def describe_signature(self, modifiers: list[Node]) -> None:
- def _add(modifiers: list[Node], text: str) -> None:
- if len(modifiers) != 0:
- modifiers.append(addnodes.desc_sig_space())
- modifiers.append(addnodes.desc_sig_keyword(text, text))
-
- if len(modifiers) != 0 and len(self.attrs) != 0:
- modifiers.append(addnodes.desc_sig_space())
- tempNode = nodes.TextElement()
- self.attrs.describe_signature(tempNode)
- modifiers.extend(tempNode.children)
- if self.storage:
- _add(modifiers, self.storage)
- if self.threadLocal:
- _add(modifiers, self.threadLocal)
- if self.inline:
- _add(modifiers, 'inline')
- if self.restrict:
- _add(modifiers, 'restrict')
- if self.volatile:
- _add(modifiers, 'volatile')
- if self.const:
- _add(modifiers, '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 TODO: remove?
- self.outer = outer
- self.leftSpecs = leftSpecs
- self.rightSpecs = rightSpecs
- self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
- self.trailingTypeSpec = trailing
-
- 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)
- modifiers: list[Node] = []
-
- self.leftSpecs.describe_signature(modifiers)
-
- for m in modifiers:
- signode += m
- if self.trailingTypeSpec:
- if len(modifiers) > 0:
- signode += addnodes.desc_sig_space()
- self.trailingTypeSpec.describe_signature(signode, mode, env,
- symbol=symbol)
- modifiers = []
- self.rightSpecs.describe_signature(modifiers)
- if len(modifiers) > 0:
- signode += addnodes.desc_sig_space()
- for m in modifiers:
- signode += m
-
-
-# Declarator
-################################################################################
-
-class ASTArray(ASTBase):
- def __init__(self, static: bool, const: bool, volatile: bool, restrict: bool,
- vla: bool, size: ASTExpression):
- self.static = static
- self.const = const
- self.volatile = volatile
- self.restrict = restrict
- self.vla = vla
- self.size = size
- if vla:
- assert size is None
- if size is not None:
- assert not vla
-
- def _stringify(self, transform: StringifyTransform) -> str:
- el = []
- if self.static:
- el.append('static')
- if self.restrict:
- el.append('restrict')
- if self.volatile:
- el.append('volatile')
- if self.const:
- el.append('const')
- if self.vla:
- return '[' + ' '.join(el) + '*]'
- elif self.size:
- el.append(transform(self.size))
- return '[' + ' '.join(el) + ']'
-
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- verify_description_mode(mode)
- signode += addnodes.desc_sig_punctuation('[', '[')
- addSpace = False
-
- 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.static:
- addSpace = _add(signode, 'static')
- if self.restrict:
- addSpace = _add(signode, 'restrict')
- if self.volatile:
- addSpace = _add(signode, 'volatile')
- if self.const:
- addSpace = _add(signode, 'const')
- if self.vla:
- signode += addnodes.desc_sig_punctuation('*', '*')
- elif self.size:
- if addSpace:
- signode += addnodes.desc_sig_space()
- 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))
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- raise NotImplementedError(repr(self))
-
- def require_space_after_declSpecs(self) -> bool:
- raise NotImplementedError(repr(self))
-
-
-class ASTDeclaratorNameParam(ASTDeclarator):
- def __init__(self, declId: ASTNestedName,
- arrayOps: list[ASTArray], param: ASTParameters) -> None:
- self.declId = declId
- self.arrayOps = arrayOps
- self.param = param
-
- @property
- def name(self) -> ASTNestedName:
- return self.declId
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- return self.param.function_params
-
- # ------------------------------------------------------------------------
-
- def require_space_after_declSpecs(self) -> bool:
- return self.declId 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.param:
- res.append(transform(self.param))
- 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.param:
- self.param.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
-
- # ------------------------------------------------------------------------
-
- def require_space_after_declSpecs(self) -> bool:
- return self.declId is not None
-
- 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, restrict: bool, volatile: bool, const: bool,
- attrs: ASTAttributeList) -> None:
- assert next
- self.next = next
- self.restrict = restrict
- self.volatile = volatile
- self.const = const
- self.attrs = attrs
-
- @property
- def name(self) -> ASTNestedName:
- return self.next.name
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- return self.next.function_params
-
- def require_space_after_declSpecs(self) -> bool:
- return self.const or self.volatile or self.restrict or \
- len(self.attrs) > 0 or \
- 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.restrict or self.volatile or self.const):
- res.append(' ')
- if self.restrict:
- res.append('restrict')
- if self.volatile:
- if self.restrict:
- res.append(' ')
- res.append('volatile')
- if self.const:
- if self.restrict or self.volatile:
- res.append(' ')
- res.append('const')
- if self.const or self.volatile or self.restrict or len(self.attrs) > 0:
- if self.next.require_space_after_declSpecs():
- res.append(' ')
- res.append(transform(self.next))
- return ''.join(res)
-
- 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.restrict or 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.restrict:
- _add_anno(signode, 'restrict')
- if self.volatile:
- if self.restrict:
- signode += addnodes.desc_sig_space()
- _add_anno(signode, 'volatile')
- if self.const:
- if self.restrict or self.volatile:
- signode += addnodes.desc_sig_space()
- _add_anno(signode, 'const')
- if self.const or self.volatile or self.restrict 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 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 and params are in inner
-
- @property
- def name(self) -> ASTNestedName:
- return self.inner.name
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- return self.inner.function_params
-
- 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 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)
-
-
-# Initializer
-################################################################################
-
-class ASTParenExprList(ASTBaseParenExprList):
- def __init__(self, exprs: list[ASTExpression]) -> None:
- self.exprs = 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 ASTBracedInitList(ASTBase):
- def __init__(self, exprs: list[ASTExpression], trailingComma: bool) -> None:
- self.exprs = exprs
- self.trailingComma = trailingComma
-
- 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 ASTInitializer(ASTBase):
- def __init__(self, value: ASTBracedInitList | ASTExpression,
- 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
-
- def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
- return symbol.get_full_nested_name().get_id(version)
-
- @property
- def function_params(self) -> list[ASTFunctionParameter]:
- return self.decl.function_params
-
- 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 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
-
- def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
- return self.type.get_id(version, objectType, symbol)
-
- 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 ASTMacroParameter(ASTBase):
- def __init__(self, arg: ASTNestedName | None, ellipsis: bool = False,
- variadic: bool = False) -> None:
- self.arg = arg
- self.ellipsis = ellipsis
- self.variadic = variadic
-
- def _stringify(self, transform: StringifyTransform) -> str:
- if self.ellipsis:
- return '...'
- elif self.variadic:
- return transform(self.arg) + '...'
- else:
- return transform(self.arg)
-
- def describe_signature(self, signode: Any, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- verify_description_mode(mode)
- if self.ellipsis:
- signode += addnodes.desc_sig_punctuation('...', '...')
- elif self.variadic:
- name = str(self)
- signode += addnodes.desc_sig_name(name, name)
- else:
- self.arg.describe_signature(signode, mode, env, symbol=symbol)
-
-
-class ASTMacro(ASTBase):
- def __init__(self, ident: ASTNestedName, args: list[ASTMacroParameter] | None) -> None:
- self.ident = ident
- self.args = args
-
- @property
- def name(self) -> ASTNestedName:
- return self.ident
-
- 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.ident))
- if self.args is not None:
- res.append('(')
- first = True
- for arg in self.args:
- if not first:
- res.append(', ')
- first = False
- res.append(transform(arg))
- res.append(')')
- return ''.join(res)
-
- def describe_signature(self, signode: TextElement, mode: str,
- env: BuildEnvironment, symbol: Symbol) -> None:
- verify_description_mode(mode)
- self.ident.describe_signature(signode, mode, env, symbol)
- if self.args is None:
- return
- paramlist = addnodes.desc_parameterlist()
- for arg in self.args:
- param = addnodes.desc_parameter('', '', noemph=True)
- arg.describe_signature(param, 'param', env, symbol=symbol)
- paramlist += param
- signode += paramlist
-
-
-class ASTStruct(ASTBase):
- def __init__(self, name: ASTNestedName) -> None:
- self.name = name
-
- 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:
- return transform(self.name)
-
- 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)
-
-
-class ASTUnion(ASTBase):
- def __init__(self, name: ASTNestedName) -> None:
- self.name = name
-
- 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:
- return transform(self.name)
-
- 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)
-
-
-class ASTEnum(ASTBase):
- def __init__(self, name: ASTNestedName) -> None:
- self.name = name
-
- 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:
- return transform(self.name)
-
- 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)
-
-
-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:
- 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)
-
-
-class ASTDeclaration(ASTBaseBase):
- def __init__(self, objectType: str, directiveType: str | None,
- declaration: DeclarationType | ASTFunctionParameter,
- semicolon: bool = False) -> None:
- self.objectType = objectType
- self.directiveType = directiveType
- self.declaration = declaration
- self.semicolon = semicolon
-
- self.symbol: Symbol = None
- # set by CObject._add_enumerator_to_parent
- self.enumeratorScopedSymbol: Symbol = None
-
- def clone(self) -> ASTDeclaration:
- return ASTDeclaration(self.objectType, self.directiveType,
- self.declaration.clone(), self.semicolon)
-
- @property
- def name(self) -> ASTNestedName:
- decl = cast(DeclarationType, self.declaration)
- return decl.name
-
- @property
- def function_params(self) -> list[ASTFunctionParameter] | None:
- if self.objectType != 'function':
- return None
- decl = cast(ASTType, self.declaration)
- return decl.function_params
-
- def get_id(self, version: int, prefixed: bool = True) -> str:
- if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
- return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
- id_ = self.declaration.get_id(version, self.objectType, self.symbol)
- if prefixed:
- return _id_prefix[version] + id_
- else:
- return id_
-
- def get_newest_id(self) -> str:
- return self.get_id(_max_id, True)
-
- def _stringify(self, transform: StringifyTransform) -> str:
- res = transform(self.declaration)
- if self.semicolon:
- res += ';'
- return res
-
- def describe_signature(self, signode: TextElement, 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
- signode += mainDeclNode
-
- if self.objectType in {'member', 'function', 'macro'}:
- pass
- elif self.objectType == 'struct':
- mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct')
- 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()
- elif self.objectType == 'enumerator':
- mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator')
- mainDeclNode += addnodes.desc_sig_space()
- elif self.objectType == 'type':
- decl = cast(ASTType, self.declaration)
- prefix = decl.get_type_declaration_prefix()
- mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix)
- mainDeclNode += addnodes.desc_sig_space()
- else:
- raise AssertionError
- self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
- if self.semicolon:
- mainDeclNode += addnodes.desc_sig_punctuation(';', ';')
-
-
-class SymbolLookupResult:
- def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol,
- ident: ASTIdentifier) -> None:
- self.symbols = symbols
- self.parentSymbol = parentSymbol
- self.ident = ident
-
-
-class LookupKey:
- def __init__(self, data: list[tuple[ASTIdentifier, str]]) -> None:
- self.data = data
-
- def __str__(self) -> str:
- inner = ', '.join(f"({ident}, {id_})" for ident, id_ in self.data)
- return f'[{inner}]'
-
-
-class Symbol:
- debug_indent = 0
- debug_indent_string = " "
- debug_lookup = False
- debug_show_tree = False
-
- 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)
-
- @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.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,
- ident: ASTIdentifier,
- 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
- self.siblingBelow: Symbol = None
- self.ident = ident
- 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_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_function_params()
-
- def _add_function_params(self) -> None:
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("_add_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 function parameters, if any
- if self.declaration is not None and self.declaration.function_params is not None:
- for p in self.declaration.function_params:
- if p.arg is None:
- continue
- nn = p.arg.name
- if nn is None:
- continue
- # (comparing to the template params: we have checked that we are a declaration)
- decl = ASTDeclaration('functionParam', None, p)
- 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:
- 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
-
- def get_all_symbols(self) -> Iterator[Symbol]:
- yield self
- for sChild in self._children:
- yield from sChild.get_all_symbols()
-
- @property
- def children(self) -> Iterator[Symbol]:
- yield from self._children
-
- @property
- def children_recurse_anon(self) -> Iterator[Symbol]:
- for c in self._children:
- yield c
- if not c.ident.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:
- if s.declaration is not None:
- # TODO: do we need the ID?
- key.append((s.ident, s.declaration.get_newest_id()))
- else:
- key.append((s.ident, 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 = []
- for s in symbols:
- names.append(s.ident)
- return ASTNestedName(names, rooted=False)
-
- def _find_first_named_symbol(self, ident: ASTIdentifier,
- matchSelf: bool, recurseInAnon: bool) -> Symbol | None:
- # TODO: further simplification from C++ to C
- if Symbol.debug_lookup:
- Symbol.debug_print("_find_first_named_symbol ->")
- res = self._find_named_symbols(ident, matchSelf, recurseInAnon,
- searchInSiblings=False)
- try:
- return next(res)
- except StopIteration:
- return None
-
- def _find_named_symbols(self, ident: ASTIdentifier,
- matchSelf: bool, recurseInAnon: bool,
- searchInSiblings: bool) -> Iterator[Symbol]:
- # TODO: further simplification from C++ to C
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("_find_named_symbols:")
- Symbol.debug_indent += 1
- Symbol.debug_print("self:")
- logger.debug(self.to_string(Symbol.debug_indent + 1), end="")
- Symbol.debug_print("ident: ", ident)
- Symbol.debug_print("matchSelf: ", matchSelf)
- Symbol.debug_print("recurseInAnon: ", recurseInAnon)
- Symbol.debug_print("searchInSiblings: ", searchInSiblings)
-
- 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 s.ident == ident:
- 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,
- onMissingQualifiedSymbol: Callable[[Symbol, ASTIdentifier], Symbol | None],
- ancestorLookupType: str | None,
- matchSelf: bool,
- recurseInAnon: bool,
- searchInSiblings: bool,
- ) -> SymbolLookupResult | None:
- # TODO: further simplification from C++ to C
- # 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("ancestorLookupType:", ancestorLookupType)
- Symbol.debug_print("matchSelf: ", matchSelf)
- Symbol.debug_print("recurseInAnon: ", recurseInAnon)
- Symbol.debug_print("searchInSiblings: ", searchInSiblings)
-
- 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]
- while parentSymbol.parent:
- if parentSymbol.find_identifier(firstName,
- matchSelf=matchSelf,
- recurseInAnon=recurseInAnon,
- searchInSiblings=searchInSiblings):
- 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
- for ident in names[:-1]:
- symbol = parentSymbol._find_first_named_symbol(
- ident, matchSelf=matchSelf, recurseInAnon=recurseInAnon)
- if symbol is None:
- symbol = onMissingQualifiedSymbol(parentSymbol, ident)
- 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
- ident = names[-1]
-
- symbols = parentSymbol._find_named_symbols(
- ident, matchSelf=matchSelf,
- recurseInAnon=recurseInAnon,
- searchInSiblings=searchInSiblings)
- if Symbol.debug_lookup:
- symbols = list(symbols) # type: ignore[assignment]
- Symbol.debug_indent -= 2
- return SymbolLookupResult(symbols, parentSymbol, ident)
-
- def _add_symbols(
- self,
- nestedName: ASTNestedName,
- declaration: ASTDeclaration | None,
- docname: str | None,
- line: int | None,
- ) -> Symbol:
- # TODO: further simplification from C++ to C
- # Used for adding a whole path of symbols, where the last may or may not
- # be an actual declaration.
-
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("_add_symbols:")
- Symbol.debug_indent += 1
- Symbol.debug_print("nn: ", nestedName)
- Symbol.debug_print("decl: ", declaration)
- Symbol.debug_print(f"location: {docname}:{line}")
-
- def onMissingQualifiedSymbol(parentSymbol: Symbol, ident: ASTIdentifier) -> Symbol:
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:")
- Symbol.debug_indent += 1
- Symbol.debug_print("ident: ", ident)
- Symbol.debug_indent -= 2
- return Symbol(parent=parentSymbol, ident=ident,
- declaration=None, docname=None, line=None)
-
- lookupResult = self._symbol_lookup(nestedName,
- onMissingQualifiedSymbol,
- ancestorLookupType=None,
- matchSelf=False,
- recurseInAnon=False,
- 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("ident: ", lookupResult.ident)
- Symbol.debug_print("declaration: ", declaration)
- Symbol.debug_print(f"location: {docname}:{line}")
- Symbol.debug_indent -= 1
- symbol = Symbol(parent=lookupResult.parentSymbol,
- ident=lookupResult.ident,
- 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,
- ident=lookupResult.ident,
- 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:
- 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, candSybmol is not None?:", candSymbol is not None,
- )
- 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
- for otherChild in other._children:
- ourChild = self._find_first_named_symbol(
- ident=otherChild.ident, matchSelf=False,
- recurseInAnon=False)
- if ourChild is None:
- # TODO: hmm, should we prune by docnames?
- self._children.append(otherChild)
- otherChild.parent = self
- otherChild._assert_invariants()
- 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 '.. c:%s:: %s'.")
- msg = msg % (ourChild.docname, ourChild.line,
- ourChild.declaration.directiveType, name)
- logger.warning(msg, location=(otherChild.docname, otherChild.line))
- else:
- # Both have declarations, and in the same docname.
- # This can apparently happen, it should be safe to
- # just ignore it, right?
- pass
- ourChild.merge_with(otherChild, docnames, env)
- if Symbol.debug_lookup:
- Symbol.debug_indent -= 1
-
- def add_name(self, nestedName: ASTNestedName) -> Symbol:
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("add_name:")
- res = self._add_symbols(nestedName, declaration=None, docname=None, 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
- res = self._add_symbols(nestedName, declaration, docname, line)
- if Symbol.debug_lookup:
- Symbol.debug_indent -= 1
- return res
-
- def find_identifier(self, ident: ASTIdentifier,
- matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool,
- ) -> Symbol | None:
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("find_identifier:")
- Symbol.debug_indent += 1
- Symbol.debug_print("ident: ", ident)
- 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.ident == ident:
- return current
- children = current.children_recurse_anon if recurseInAnon else current._children
- for s in children:
- if s.ident == ident:
- return s
- if not searchInSiblings:
- break
- current = current.siblingAbove
- return None
-
- def direct_lookup(self, key: LookupKey) -> Symbol | None:
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("direct_lookup:")
- Symbol.debug_indent += 1
- s = self
- for name, id_ in key.data:
- res = None
- for cand in s._children:
- if cand.ident == name:
- res = cand
- break
- s = res
- if Symbol.debug_lookup:
- Symbol.debug_print("name: ", name)
- 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_declaration(self, nestedName: ASTNestedName, typ: str,
- matchSelf: bool, recurseInAnon: bool) -> Symbol | None:
- # templateShorthand: missing template parameter lists for templates is ok
- if Symbol.debug_lookup:
- Symbol.debug_indent += 1
- Symbol.debug_print("find_declaration:")
-
- def onMissingQualifiedSymbol(
- parentSymbol: Symbol,
- ident: ASTIdentifier,
- ) -> Symbol | None:
- return None
-
- lookupResult = self._symbol_lookup(nestedName,
- onMissingQualifiedSymbol,
- ancestorLookupType=typ,
- matchSelf=matchSelf,
- recurseInAnon=recurseInAnon,
- 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
- return symbols[0]
-
- def to_string(self, indent: int) -> str:
- res = [Symbol.debug_indent_string * indent]
- if not self.parent:
- res.append('::')
- else:
- if self.ident:
- res.append(str(self.ident))
- else:
- res.append(str(self.declaration))
- if self.declaration:
- res.append(": ")
- if self.isRedeclaration:
- res.append('!!duplicate!! ')
- 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.c_id_attributes
-
- @property
- def paren_attributes(self):
- return self.config.c_paren_attributes
-
- def _parse_string(self) -> str | None:
- 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 | None:
- # -> integer-literal
- # | character-literal
- # | floating-literal
- # | string-literal
- # | boolean-literal -> "false" | "true"
- self.skip_ws()
- 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):
- self.match(float_literal_suffix_re)
- return ASTNumberLiteral(self.definition[pos:self.pos])
- for regex in [binary_literal_re, hex_literal_re,
- integer_literal_re, octal_literal_re]:
- if self.match(regex):
- self.match(integers_literal_suffix_re)
- return ASTNumberLiteral(self.definition[pos:self.pos])
-
- string = self._parse_string()
- if string is not None:
- return 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:
- return 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 None
-
- def _parse_paren_expression(self) -> ASTExpression | None:
- # "(" expression ")"
- if self.current_char != '(':
- return None
- self.pos += 1
- res = self._parse_expression()
- self.skip_ws()
- if not self.skip_string(')'):
- self.fail("Expected ')' in end of parenthesized expression.")
- return ASTParenExpr(res)
-
- def _parse_primary_expression(self) -> ASTExpression | None:
- # literal
- # "(" expression ")"
- # id-expression -> we parse this with _parse_nested_name
- self.skip_ws()
- res: ASTExpression | None = self._parse_literal()
- if res is not None:
- return res
- res = self._parse_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], bool]:
- # Parse open and close with the actual initializer-list in between
- # -> initializer-clause '...'[opt]
- # | initializer-list ',' initializer-clause '...'[opt]
- # TODO: designators
- self.skip_ws()
- if not self.skip_string_and_ws(open):
- return None, None
- if self.skip_string(close):
- return [], False
-
- exprs = []
- trailingComma = False
- while True:
- self.skip_ws()
- expr = self._parse_expression()
- self.skip_ws()
- 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 | None:
- # -> '(' 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_braced_init_list(self) -> ASTBracedInitList | None:
- # -> '{' initializer-list ','[opt] '}'
- # | '{' '}'
- exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
- if exprs is None:
- return None
- return ASTBracedInitList(exprs, trailingComma)
-
- def _parse_postfix_expression(self) -> ASTPostfixExpr:
- # -> primary
- # | postfix "[" expression "]"
- # | postfix "[" braced-init-list [opt] "]"
- # | postfix "(" expression-list [opt] ")"
- # | postfix "." id-expression // taken care of in primary by nested name
- # | postfix "->" id-expression
- # | postfix "++"
- # | postfix "--"
-
- prefix = self._parse_primary_expression()
-
- # and now parse postfixes
- postFixes: list[ASTPostfixOp] = []
- while True:
- self.skip_ws()
- 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 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_paren_expression_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 ")"
- # | "alignof" "(" type-id ")"
- 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('('):
- 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)
- 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) -> 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, opId):
- if opId + 1 == len(_expression_bin_ops):
- def parser() -> ASTExpression:
- return self._parse_cast_expression()
- else:
- def parser() -> ASTExpression:
- return _parse_bin_op_expr(self, opId + 1)
- exprs = []
- ops = []
- exprs.append(parser())
- while True:
- self.skip_ws()
- 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()
- 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)
-
- def _parse_conditional_expression_tail(self, orExprHead: Any) -> ASTExpression | None:
- # -> "?" expression ":" assignment-expression
- return None
-
- def _parse_assignment_expression(self) -> ASTExpression:
- # -> conditional-expression
- # | logical-or-expression assignment-operator initializer-clause
- # -> conditional-expression ->
- # logical-or-expression
- # | logical-or-expression "?" expression ":" assignment-expression
- # | logical-or-expression assignment-operator initializer-clause
- exprs = []
- ops = []
- orExpr = self._parse_logical_or_expression()
- exprs.append(orExpr)
- # TODO: handle ternary with _parse_conditional_expression_tail
- while True:
- oneMore = False
- self.skip_ws()
- for op in _expression_assignment_ops:
- if op[0] in 'abcnox':
- if not self.skip_word(op):
- continue
- else:
- if not self.skip_string(op):
- continue
- expr = self._parse_logical_or_expression()
- exprs.append(expr)
- ops.append(op)
- oneMore = True
- if not oneMore:
- break
- return ASTAssignmentExpr(exprs, ops)
-
- def _parse_constant_expression(self) -> ASTExpression:
- # -> conditional-expression
- orExpr = self._parse_logical_or_expression()
- # TODO: use _parse_conditional_expression_tail
- return orExpr
-
- def _parse_expression(self) -> ASTExpression:
- # -> assignment-expression
- # | expression "," assignment-expression
- # TODO: actually parse the second production
- return self._parse_assignment_expression()
-
- 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_nested_name(self) -> ASTNestedName:
- names: list[Any] = []
-
- self.skip_ws()
- rooted = False
- if self.skip_string('.'):
- rooted = True
- while 1:
- self.skip_ws()
- if not self.match(identifier_re):
- 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)
- if self.matched_text in self.config.c_extra_keywords:
- msg = "Expected identifier, got user-defined keyword: %s." \
- + " Remove it from c_extra_keywords to allow it as identifier.\n" \
- + "Currently c_extra_keywords is %s."
- self.fail(msg % (self.matched_text,
- str(self.config.c_extra_keywords)))
- ident = ASTIdentifier(identifier)
- names.append(ident)
-
- self.skip_ws()
- if not self.skip_string('.'):
- break
- return ASTNestedName(names, rooted)
-
- def _parse_simple_type_specifier(self) -> str | None:
- if self.match(_simple_type_specifiers_re):
- return self.matched_text
- for t in ('bool', 'complex', 'imaginary'):
- if t in self.config.c_extra_keywords:
- if self.skip_word(t):
- return t
- return None
-
- def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental | None:
- names: list[str] = []
-
- self.skip_ws()
- while True:
- t = self._parse_simple_type_specifier()
- if t is None:
- break
- names.append(t)
- self.skip_ws()
- if len(names) == 0:
- return None
- return ASTTrailingTypeSpecFundamental(names)
-
- def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
- # fundamental types, https://en.cppreference.com/w/c/language/type
- # and extensions
- self.skip_ws()
- res = self._parse_simple_type_specifiers()
- if res is not None:
- return res
-
- # prefixed
- prefix = None
- self.skip_ws()
- for k in ('struct', 'enum', 'union'):
- if self.skip_word_and_ws(k):
- prefix = k
- break
-
- nestedName = self._parse_nested_name()
- return ASTTrailingTypeSpecName(prefix, nestedName)
-
- def _parse_parameters(self, paramMode: str) -> ASTParameters | None:
- self.skip_ws()
- if not self.skip_string('('):
- if paramMode == 'function':
- self.fail('Expecting "(" in parameters.')
- 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.')
- 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(f'Expecting "," or ")" in parameters, got "{self.current_char}".')
-
- attrs = self._parse_attribute_list()
- return ASTParameters(args, attrs)
-
- def _parse_decl_specs_simple(
- self, outer: str | None, typed: bool,
- ) -> ASTDeclSpecsSimple:
- """Just parse the simple ones."""
- storage = None
- threadLocal = None
- inline = None
- restrict = None
- volatile = None
- const = None
- attrs = []
- while 1: # accept any permutation of a subset of some decl-specs
- self.skip_ws()
- if not storage:
- if outer == 'member':
- if self.skip_word('auto'):
- storage = 'auto'
- continue
- if self.skip_word('register'):
- storage = 'register'
- continue
- if outer in ('member', 'function'):
- if self.skip_word('static'):
- storage = 'static'
- continue
- if self.skip_word('extern'):
- storage = 'extern'
- continue
- if outer == 'member' and not threadLocal:
- if self.skip_word('thread_local'):
- threadLocal = 'thread_local'
- continue
- if self.skip_word('_Thread_local'):
- threadLocal = '_Thread_local'
- continue
- if outer == 'function' and not inline:
- inline = self.skip_word('inline')
- if inline:
- continue
-
- if not restrict and typed:
- restrict = self.skip_word('restrict')
- if restrict:
- continue
- if not volatile and typed:
- volatile = self.skip_word('volatile')
- if volatile:
- continue
- if not const and typed:
- const = self.skip_word('const')
- if const:
- continue
- attr = self._parse_attribute()
- if attr:
- attrs.append(attr)
- continue
- break
- return ASTDeclSpecsSimple(storage, threadLocal, inline,
- restrict, volatile, const, ASTAttributeList(attrs))
-
- def _parse_decl_specs(self, outer: str | None, typed: bool = True) -> ASTDeclSpecs:
- if outer:
- if outer not in ('type', 'member', 'function'):
- raise Exception('Internal error, unknown outer "%s".' % outer)
- 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,
- ) -> ASTDeclarator:
- assert named in (True, False, 'single')
- # now we should parse the name, and then suffixes
- if named == 'single':
- if self.match(identifier_re):
- if self.matched_text in _keywords:
- self.fail("Expected identifier, "
- "got keyword: %s" % self.matched_text)
- if self.matched_text in self.config.c_extra_keywords:
- msg = "Expected identifier, got user-defined keyword: %s." \
- + " Remove it from c_extra_keywords to allow it as identifier.\n" \
- + "Currently c_extra_keywords is %s."
- self.fail(msg % (self.matched_text,
- str(self.config.c_extra_keywords)))
- identifier = ASTIdentifier(self.matched_text)
- declId = ASTNestedName([identifier], rooted=False)
- 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()
- static = False
- const = False
- volatile = False
- restrict = False
- while True:
- if not static:
- if self.skip_word_and_ws('static'):
- static = True
- continue
- if not const:
- if self.skip_word_and_ws('const'):
- const = True
- continue
- if not volatile:
- if self.skip_word_and_ws('volatile'):
- volatile = True
- continue
- if not restrict:
- if self.skip_word_and_ws('restrict'):
- restrict = True
- continue
- break
- vla = False if static else self.skip_string_and_ws('*')
- if vla:
- if not self.skip_string(']'):
- self.fail("Expected ']' in end of array operator.")
- size = None
- else:
- if self.skip_string(']'):
- size = None
- else:
-
- def parser():
- return self._parse_expression()
- size = self._parse_expression_fallback([']'], parser)
- self.skip_ws()
- if not self.skip_string(']'):
- self.fail("Expected ']' in end of array operator.")
- arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size))
- else:
- break
- param = self._parse_parameters(paramMode)
- if param 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()
- return ASTDeclaratorNameBitField(declId=declId, size=size)
- return ASTDeclaratorNameParam(declId=declId, arrayOps=arrayOps,
- param=param)
-
- 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'):
- raise Exception(
- "Internal error, unknown paramMode '%s'." % paramMode)
- prevErrors = []
- self.skip_ws()
- if typed and self.skip_string('*'):
- self.skip_ws()
- restrict = False
- volatile = False
- const = False
- attrs = []
- while 1:
- if not restrict:
- restrict = self.skip_word_and_ws('restrict')
- if restrict:
- continue
- 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:
- attrs.append(attr)
- continue
- break
- next = self._parse_declarator(named, paramMode, typed)
- return ASTDeclaratorPtr(next=next,
- restrict=restrict, volatile=volatile, const=const,
- attrs=ASTAttributeList(attrs))
- if typed and self.current_char == '(': # note: peeking, not skipping
- # maybe this is the beginning of params, try that first,
- # otherwise assume it's noptr->declarator > ( ptr-declarator )
- pos = self.pos
- try:
- # assume this is params
- res = self._parse_declarator_name_suffix(named, paramMode,
- typed)
- return res
- except DefinitionError as exParamQual:
- msg = "If declarator-id with parameters"
- if paramMode == 'function':
- msg += " (e.g., 'void f(int arg)')"
- prevErrors.append((exParamQual, msg))
- 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 b
- # 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
- msg = "If parenthesis in noptr-declarator"
- if paramMode == 'function':
- msg += " (e.g., 'void (*f(int arg))(double)')"
- prevErrors.append((exNoPtrParen, msg))
- header = "Error in declarator"
- raise self._make_multi_error(prevErrors, header) from exNoPtrParen
- pos = self.pos
- try:
- return self._parse_declarator_name_suffix(named, paramMode, typed)
- except DefinitionError as e:
- self.pos = pos
- prevErrors.append((e, "If declarator-id"))
- header = "Error in declarator or parameters"
- raise self._make_multi_error(prevErrors, header) from e
-
- def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
- ) -> ASTInitializer | None:
- self.skip_ws()
- if outer == 'member' and False: # NoQA: SIM223 # TODO
- 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 is None: # function parameter
- fallbackEnd = [',', ')']
- else:
- self.fail("Internal error, initializer for outer '%s' not "
- "implemented." % outer)
-
- def parser():
- return self._parse_assignment_expression()
-
- 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|'single'|True: 'single' is e.g., for function objects which
- doesn't need to name the arguments, but otherwise is a single name
- """
- if outer: # always named
- if outer not in ('type', 'member', 'function'):
- raise Exception('Internal error, unknown outer "%s".' % outer)
- assert named
-
- if outer == 'type':
- # We allow type objects to just be a name.
- 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)
- self.assert_end(allowSemicolon=True)
- except DefinitionError as exUntyped:
- desc = "If just a name"
- 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
- desc = "If typedef-like declaration"
- 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:
- header = "Type must be either just a name or a "
- header += "typedef-like declaration."
- 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)
- elif outer == 'function':
- declSpecs = self._parse_decl_specs(outer=outer)
- decl = self._parse_declarator(named=True, paramMode=outer)
- else:
- paramMode = 'type'
- if outer == 'member': # i.e., member
- named = True
- 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 | None) -> ASTTypeWithInit:
- if outer:
- assert outer in ('type', 'member', 'function')
- type = self._parse_type(outer=outer, named=named)
- init = self._parse_initializer(outer=outer)
- return ASTTypeWithInit(type, init)
-
- def _parse_macro(self) -> ASTMacro:
- self.skip_ws()
- ident = self._parse_nested_name()
- if ident is None:
- self.fail("Expected identifier in macro definition.")
- self.skip_ws()
- if not self.skip_string_and_ws('('):
- return ASTMacro(ident, None)
- if self.skip_string(')'):
- return ASTMacro(ident, [])
- args = []
- while 1:
- self.skip_ws()
- if self.skip_string('...'):
- args.append(ASTMacroParameter(None, True))
- self.skip_ws()
- if not self.skip_string(')'):
- self.fail('Expected ")" after "..." in macro parameters.')
- break
- if not self.match(identifier_re):
- self.fail("Expected identifier in macro parameters.")
- nn = ASTNestedName([ASTIdentifier(self.matched_text)], rooted=False)
- # Allow named variadic args:
- # https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
- self.skip_ws()
- if self.skip_string_and_ws('...'):
- args.append(ASTMacroParameter(nn, False, True))
- self.skip_ws()
- if not self.skip_string(')'):
- self.fail('Expected ")" after "..." in macro parameters.')
- break
- args.append(ASTMacroParameter(nn))
- if self.skip_string_and_ws(','):
- continue
- if self.skip_string_and_ws(')'):
- break
- self.fail("Expected identifier, ')', or ',' in macro parameter list.")
- return ASTMacro(ident, args)
-
- def _parse_struct(self) -> ASTStruct:
- name = self._parse_nested_name()
- return ASTStruct(name)
-
- def _parse_union(self) -> ASTUnion:
- name = self._parse_nested_name()
- return ASTUnion(name)
-
- def _parse_enum(self) -> ASTEnum:
- name = self._parse_nested_name()
- return ASTEnum(name)
-
- 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()
-
- initVal = self._parse_expression_fallback([], parser)
- init = ASTInitializer(initVal)
- return ASTEnumerator(name, init, attrs)
-
- def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
- if objectType not in ('function', 'member',
- 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
- raise Exception('Internal error, unknown objectType "%s".' % objectType)
- if directiveType not in ('function', 'member', 'var',
- 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
- raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
-
- declaration: DeclarationType = None
- if objectType == 'member':
- declaration = self._parse_type_with_init(named=True, outer='member')
- elif objectType == 'function':
- declaration = self._parse_type(named=True, outer='function')
- elif objectType == 'macro':
- declaration = self._parse_macro()
- elif objectType == 'struct':
- declaration = self._parse_struct()
- elif objectType == 'union':
- declaration = self._parse_union()
- elif objectType == 'enum':
- declaration = self._parse_enum()
- elif objectType == 'enumerator':
- declaration = self._parse_enumerator()
- elif objectType == 'type':
- declaration = self._parse_type(named=True, outer='type')
- else:
- raise AssertionError
- if objectType != 'macro':
- self.skip_ws()
- semicolon = self.skip_string(';')
- else:
- semicolon = False
- return ASTDeclaration(objectType, directiveType, declaration, semicolon)
-
- def parse_namespace_object(self) -> ASTNestedName:
- return self._parse_nested_name()
-
- def parse_xref_object(self) -> ASTNestedName:
- name = self._parse_nested_name()
- # if there are '()' left, just skip them
- self.skip_ws()
- self.skip_string('()')
- self.assert_end()
- return name
-
- def parse_expression(self) -> ASTExpression | ASTType:
- pos = self.pos
- res: ASTExpression | ASTType = None
- try:
- res = self._parse_expression()
- self.skip_ws()
- self.assert_end()
- except DefinitionError as exExpr:
- self.pos = pos
- try:
- res = self._parse_type(False)
- self.skip_ws()
- self.assert_end()
- 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
- return res
-
-
-def _make_phony_error_name() -> ASTNestedName:
- return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False)
-
-
-class CObject(ObjectDescription[ASTDeclaration]):
- """
- Description of a C language object.
- """
-
- option_spec: OptionSpec = {
- 'no-index-entry': directives.flag,
- 'no-contents-entry': directives.flag,
- 'no-typesetting': directives.flag,
- 'noindexentry': directives.flag,
- 'nocontentsentry': 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
- # then add the name to the parent scope
- symbol = ast.symbol
- assert symbol
- assert symbol.ident is not None
- parentSymbol = symbol.parent
- assert parentSymbol
- if parentSymbol.parent is None:
- # TODO: we could warn, but it is somewhat equivalent to
- # enumeratorss, without the enum
- return # no parent
- parentDecl = parentSymbol.declaration
- if parentDecl is None:
- # the parent is not explicitly declared
- # TODO: we could warn, but?
- return
- if parentDecl.objectType != 'enum':
- # TODO: maybe issue a warning, enumerators in non-enums is weird,
- # but it is somewhat equivalent to enumeratorss, without the enum
- return
- if parentDecl.directiveType != 'enum':
- return
-
- targetSymbol = parentSymbol.parent
- s = targetSymbol.find_identifier(symbol.ident, 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, ident=symbol.ident,
- 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:
- 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
-
- name = ast.symbol.get_full_nested_name().get_display_string().lstrip('.')
- if newestId not in self.state.document.ids:
- # 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)
-
- if 'no-index-entry' not in self.options:
- indexText = self.get_index_text(name)
- self.indexnode['entries'].append(('single', indexText, newestId, '', None))
-
- @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: TextElement, 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 'c:parent_symbol' not in env.temp_data:
- root = env.domaindata['c']['root_symbol']
- env.temp_data['c:parent_symbol'] = root
- env.ref_context['c:parent_key'] = root.get_lookup_key()
-
- # 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['c:last_symbol'] = None
- return super().run()
-
- def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
- parentSymbol: Symbol = self.env.temp_data['c:parent_symbol']
-
- max_len = (self.env.config.c_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['c: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['c:last_symbol']
- if symbol.siblingAbove is not None:
- assert symbol.siblingAbove.siblingBelow is None
- symbol.siblingAbove.siblingBelow = symbol
- self.env.temp_data['c: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['c:last_symbol'] = e.symbol
- msg = __("Duplicate C declaration, also defined at %s:%s.\n"
- "Declaration is '.. c:%s:: %s'.")
- msg = msg % (e.symbol.docname, e.symbol.line, self.display_object_type, sig)
- logger.warning(msg, location=signode)
-
- if ast.objectType == 'enumerator':
- 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)
- self.describe_signature(signode, ast, options)
- return ast
-
- def before_content(self) -> None:
- lastSymbol: Symbol = self.env.temp_data['c:last_symbol']
- assert lastSymbol
- self.oldParentSymbol = self.env.temp_data['c:parent_symbol']
- self.oldParentKey: LookupKey = self.env.ref_context['c:parent_key']
- self.env.temp_data['c:parent_symbol'] = lastSymbol
- self.env.ref_context['c:parent_key'] = lastSymbol.get_lookup_key()
-
- def after_content(self) -> None:
- self.env.temp_data['c:parent_symbol'] = self.oldParentSymbol
- self.env.ref_context['c:parent_key'] = self.oldParentKey
-
-
-class CMemberObject(CObject):
- object_type = 'member'
-
- @property
- def display_object_type(self) -> str:
- # the distinction between var and member is only cosmetic
- assert self.objtype in ('member', 'var')
- return self.objtype
-
-
-_function_doc_field_types = [
- TypedField('parameter', label=_('Parameters'),
- names=('param', 'parameter', 'arg', 'argument'),
- typerolename='expr', typenames=('type',)),
- GroupedField('retval', label=_('Return values'),
- names=('retvals', 'retval'),
- can_collapse=True),
- Field('returnvalue', label=_('Returns'), has_arg=False,
- names=('returns', 'return')),
- Field('returntype', label=_('Return type'), has_arg=False,
- names=('rtype',)),
-]
-
-
-class CFunctionObject(CObject):
- object_type = 'function'
-
- doc_field_types = _function_doc_field_types.copy()
-
-
-class CMacroObject(CObject):
- object_type = 'macro'
-
- doc_field_types = _function_doc_field_types.copy()
-
-
-class CStructObject(CObject):
- object_type = 'struct'
-
-
-class CUnionObject(CObject):
- object_type = 'union'
-
-
-class CEnumObject(CObject):
- object_type = 'enum'
-
-
-class CEnumeratorObject(CObject):
- object_type = 'enumerator'
-
-
-class CTypeObject(CObject):
- object_type = 'type'
-
-
-class CNamespaceObject(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['c']['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.env.config)
- try:
- name = parser.parse_namespace_object()
- parser.assert_end()
- except DefinitionError as e:
- logger.warning(e, location=self.get_location())
- name = _make_phony_error_name()
- symbol = rootSymbol.add_name(name)
- stack = [symbol]
- self.env.temp_data['c:parent_symbol'] = symbol
- self.env.temp_data['c:namespace_stack'] = stack
- self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
- return []
-
-
-class CNamespacePushObject(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.env.config)
- try:
- name = parser.parse_namespace_object()
- parser.assert_end()
- except DefinitionError as e:
- logger.warning(e, location=self.get_location())
- name = _make_phony_error_name()
- oldParent = self.env.temp_data.get('c:parent_symbol', None)
- if not oldParent:
- oldParent = self.env.domaindata['c']['root_symbol']
- symbol = oldParent.add_name(name)
- stack = self.env.temp_data.get('c:namespace_stack', [])
- stack.append(symbol)
- self.env.temp_data['c:parent_symbol'] = symbol
- self.env.temp_data['c:namespace_stack'] = stack
- self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
- return []
-
-
-class CNamespacePopObject(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('c: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['c']['root_symbol']
- self.env.temp_data['c:parent_symbol'] = symbol
- self.env.temp_data['c:namespace_stack'] = stack
- self.env.ref_context['cp:parent_key'] = symbol.get_lookup_key()
- return []
-
-
-class AliasNode(nodes.Element):
- def __init__(
- self,
- sig: str,
- aliasOptions: dict,
- document: Any,
- env: BuildEnvironment | None = None,
- parentKey: LookupKey | None = None,
- ) -> None:
- super().__init__()
- self.sig = sig
- self.aliasOptions = aliasOptions
- self.document = document
- if env is not None:
- if 'c:parent_symbol' not in env.temp_data:
- root = env.domaindata['c']['root_symbol']
- env.temp_data['c:parent_symbol'] = root
- env.ref_context['c:parent_key'] = root.get_lookup_key()
- self.parentKey = env.ref_context['c:parent_key']
- else:
- assert parentKey is not None
- self.parentKey = parentKey
-
- def copy(self) -> AliasNode:
- return self.__class__(self.sig, self.aliasOptions, self.document,
- 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'] = 'c'
- # '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
- 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)
- name = parser.parse_xref_object()
- except DefinitionError as e:
- logger.warning(e, location=node)
- name = None
-
- if name 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['c'].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
-
- s = parentSymbol.find_declaration(
- name, 'any',
- matchSelf=True, recurseInAnon=True)
- if s is None:
- signode = addnodes.desc_signature(sig, '')
- node.append(signode)
- signode.clear()
- signode += addnodes.desc_name(sig, sig)
-
- logger.warning("Could not find C declaration for alias '%s'." % name,
- location=node)
- node.replace_self(signode)
- continue
- # Declarations like .. var:: int Missing::var
- # may introduce symbols without declarations.
- # But if we skip the root then it is ok to start recursion from it.
- if not node.aliasOptions['noroot'] and s.declaration is None:
- signode = addnodes.desc_signature(sig, '')
- node.append(signode)
- signode.clear()
- signode += addnodes.desc_name(sig, sig)
-
- logger.warning(
- "Can not render C declaration for alias '%s'. No such declaration." % name,
- location=node)
- node.replace_self(signode)
- continue
-
- nodes = self._render_symbol(s, maxdepth=node.aliasOptions['maxdepth'],
- skipThis=node.aliasOptions['noroot'],
- aliasOptions=node.aliasOptions,
- renderOptions={}, document=node.document)
- node.replace_self(nodes)
-
-
-class CAliasObject(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 if 'noroot'.
- 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
- node['no-index'] = True
-
- 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())
- for sig in self.get_signatures():
- node.append(AliasNode(sig, aliasOptions, self.state.document, env=self.env))
- return [node]
-
-
-class CXRefRole(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 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[0:1] == '~':
- title = title[1:]
- dot = title.rfind('.')
- if dot != -1:
- title = title[dot + 1:]
- return title, target
-
-
-class CExprRole(SphinxRole):
- def __init__(self, asCode: bool) -> None:
- super().__init__()
- if asCode:
- # render the expression as inline code
- self.class_type = 'c-expr'
- else:
- # render the expression as inline text
- self.class_type = 'c-texpr'
-
- def run(self) -> tuple[list[Node], list[system_message]]:
- text = self.text.replace('\n', ' ')
- parser = DefinitionParser(text, location=self.get_location(),
- config=self.env.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('c', text, text, classes=[self.class_type])], []
- parentSymbol = self.env.temp_data.get('c:parent_symbol', None)
- if parentSymbol is None:
- parentSymbol = self.env.domaindata['c']['root_symbol']
- # ...most if not all of these classes should really apply to the individual references,
- # not the container node
- signode = addnodes.desc_inline('c', classes=[self.class_type])
- ast.describe_signature(signode, 'markType', self.env, parentSymbol)
- return [signode], []
-
-
-class CDomain(Domain):
- """C language domain."""
- name = 'c'
- label = 'C'
- object_types = {
- # 'identifier' is the one used for xrefs generated in signatures, not in roles
- 'member': ObjType(_('member'), 'var', 'member', 'data', 'identifier'),
- 'var': ObjType(_('variable'), 'var', 'member', 'data', 'identifier'),
- 'function': ObjType(_('function'), 'func', 'identifier', 'type'),
- 'macro': ObjType(_('macro'), 'macro', 'identifier'),
- 'struct': ObjType(_('struct'), 'struct', 'identifier', 'type'),
- 'union': ObjType(_('union'), 'union', 'identifier', 'type'),
- 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'),
- 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'),
- 'type': ObjType(_('type'), 'identifier', 'type'),
- # generated object types
- 'functionParam': ObjType(_('function parameter'), 'identifier', 'var', 'member', 'data'), # noqa: E501
- }
-
- directives = {
- 'member': CMemberObject,
- 'var': CMemberObject,
- 'function': CFunctionObject,
- 'macro': CMacroObject,
- 'struct': CStructObject,
- 'union': CUnionObject,
- 'enum': CEnumObject,
- 'enumerator': CEnumeratorObject,
- 'type': CTypeObject,
- # scope control
- 'namespace': CNamespaceObject,
- 'namespace-push': CNamespacePushObject,
- 'namespace-pop': CNamespacePopObject,
- # other
- 'alias': CAliasObject,
- }
- roles = {
- 'member': CXRefRole(),
- 'data': CXRefRole(),
- 'var': CXRefRole(),
- 'func': CXRefRole(fix_parens=True),
- 'macro': CXRefRole(),
- 'struct': CXRefRole(),
- 'union': CXRefRole(),
- 'enum': CXRefRole(),
- 'enumerator': CXRefRole(),
- 'type': CXRefRole(),
- 'expr': CExprRole(asCode=True),
- 'texpr': CExprRole(asCode=False),
- }
- initial_data: dict[str, Symbol | dict[str, tuple[str, str, str]]] = {
- 'root_symbol': Symbol(None, None, None, None, None),
- 'objects': {}, # fullname -> docname, node_id, objtype
- }
-
- 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)
-
- 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")
- logger.debug("merge_domaindata end")
-
- self.data['root_symbol'].merge_with(otherdata['root_symbol'],
- docnames, self.env)
- ourObjects = self.data['objects']
- for fullname, (fn, id_, objtype) in otherdata['objects'].items():
- if fn in docnames:
- if fullname not in ourObjects:
- ourObjects[fullname] = (fn, id_, objtype)
- # no need to warn on duplicates, the symbol merge already does that
-
- 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]:
- parser = DefinitionParser(target, location=node, config=env.config)
- try:
- name = parser.parse_xref_object()
- except DefinitionError as e:
- logger.warning('Unparseable C cross-reference: %r\n%s', target, e,
- location=node)
- return None, None
- parentKey: LookupKey = node.get("c: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)
- logger.debug(rootSymbol.dump(1))
- assert parentSymbol # should be there
- else:
- parentSymbol = rootSymbol
- s = parentSymbol.find_declaration(name, typ,
- matchSelf=True, recurseInAnon=True)
- if s is None or s.declaration is None:
- return None, None
-
- # TODO: check role type vs. object type
-
- declaration = s.declaration
- displayName = name.get_display_string()
- docname = s.docname
- assert docname
-
- return make_refnode(builder, fromdocname, docname,
- declaration.get_newest_id(), contnode, displayName,
- ), declaration.objectType
-
- 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:
- return [('c:' + 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 setup(app: Sphinx) -> dict[str, Any]:
- app.add_domain(CDomain)
- app.add_config_value("c_id_attributes", [], 'env')
- app.add_config_value("c_paren_attributes", [], 'env')
- app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
- app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None})
- app.add_post_transform(AliasTransform)
-
- return {
- 'version': 'builtin',
- 'env_version': 3,
- 'parallel_read_safe': True,
- 'parallel_write_safe': True,
- }
diff --git a/sphinx/domains/c/__init__.py b/sphinx/domains/c/__init__.py
new file mode 100644
index 0000000..903fa4c
--- /dev/null
+++ b/sphinx/domains/c/__init__.py
@@ -0,0 +1,849 @@
+"""The C language domain."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, ClassVar
+
+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.domains.c._ast import (
+ ASTDeclaration,
+ ASTIdentifier,
+ ASTNestedName,
+)
+from sphinx.domains.c._ids import _macroKeywords, _max_id
+from sphinx.domains.c._parser import DefinitionParser
+from sphinx.domains.c._symbol import Symbol, _DuplicateSymbolError
+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 (
+ DefinitionError,
+ NoOldIdError,
+ anon_identifier_re,
+)
+from sphinx.util.docfields import Field, GroupedField, TypedField
+from sphinx.util.docutils import SphinxDirective
+from sphinx.util.nodes import make_refnode
+
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+ from docutils.nodes import Element, Node, TextElement, system_message
+
+ from sphinx.addnodes import pending_xref
+ from sphinx.application import Sphinx
+ from sphinx.builders import Builder
+ from sphinx.domains.c._symbol import LookupKey
+ from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
+
+# re-export objects for backwards compatibility
+# xref https://github.com/sphinx-doc/sphinx/issues/12295
+from sphinx.domains.c._ast import ( # NoQA: F401
+ ASTAlignofExpr,
+ ASTArray,
+ ASTAssignmentExpr,
+ ASTBase,
+ ASTBinOpExpr,
+ ASTBooleanLiteral,
+ ASTBracedInitList,
+ ASTCastExpr,
+ ASTCharLiteral,
+ ASTDeclarator,
+ ASTDeclaratorNameBitField,
+ ASTDeclaratorNameParam,
+ ASTDeclaratorParen,
+ ASTDeclaratorPtr,
+ ASTDeclSpecs,
+ ASTDeclSpecsSimple,
+ ASTEnum,
+ ASTEnumerator,
+ ASTExpression,
+ ASTFallbackExpr,
+ ASTFunctionParameter,
+ ASTIdExpression,
+ ASTInitializer,
+ ASTLiteral,
+ ASTMacro,
+ ASTMacroParameter,
+ ASTNumberLiteral,
+ ASTParameters,
+ ASTParenExpr,
+ ASTParenExprList,
+ ASTPostfixArray,
+ ASTPostfixCallExpr,
+ ASTPostfixDec,
+ ASTPostfixExpr,
+ ASTPostfixInc,
+ ASTPostfixMemberOfPointer,
+ ASTPostfixOp,
+ ASTSizeofExpr,
+ ASTSizeofType,
+ ASTStringLiteral,
+ ASTStruct,
+ ASTTrailingTypeSpec,
+ ASTTrailingTypeSpecFundamental,
+ ASTTrailingTypeSpecName,
+ ASTType,
+ ASTTypeWithInit,
+ ASTUnaryOpExpr,
+ ASTUnion,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def _make_phony_error_name() -> ASTNestedName:
+ return ASTNestedName([ASTIdentifier("PhonyNameDueToError")], rooted=False)
+
+
+class CObject(ObjectDescription[ASTDeclaration]):
+ """
+ Description of a C language object.
+ """
+
+ option_spec: ClassVar[OptionSpec] = {
+ 'no-index-entry': directives.flag,
+ 'no-contents-entry': directives.flag,
+ 'no-typesetting': directives.flag,
+ 'noindexentry': directives.flag,
+ 'nocontentsentry': 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
+ # then add the name to the parent scope
+ symbol = ast.symbol
+ assert symbol
+ assert symbol.ident is not None
+ parentSymbol = symbol.parent
+ assert parentSymbol
+ if parentSymbol.parent is None:
+ # TODO: we could warn, but it is somewhat equivalent to
+ # enumeratorss, without the enum
+ return # no parent
+ parentDecl = parentSymbol.declaration
+ if parentDecl is None:
+ # the parent is not explicitly declared
+ # TODO: we could warn, but?
+ return
+ if parentDecl.objectType != 'enum':
+ # TODO: maybe issue a warning, enumerators in non-enums is weird,
+ # but it is somewhat equivalent to enumeratorss, without the enum
+ return
+ if parentDecl.directiveType != 'enum':
+ return
+
+ targetSymbol = parentSymbol.parent
+ s = targetSymbol.find_identifier(symbol.ident, 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, ident=symbol.ident,
+ 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:
+ 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.reverse()
+ newestId = ids[0]
+ assert newestId # shouldn't be None
+
+ name = ast.symbol.get_full_nested_name().get_display_string().lstrip('.')
+ if newestId not in self.state.document.ids:
+ # 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)
+
+ if 'no-index-entry' not in self.options:
+ indexText = self.get_index_text(name)
+ self.indexnode['entries'].append(('single', indexText, newestId, '', None))
+
+ @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: TextElement, 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 'c:parent_symbol' not in env.temp_data:
+ root = env.domaindata['c']['root_symbol']
+ env.temp_data['c:parent_symbol'] = root
+ env.ref_context['c:parent_key'] = root.get_lookup_key()
+
+ # 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['c:last_symbol'] = None
+ return super().run()
+
+ def handle_signature(self, sig: str, signode: TextElement) -> ASTDeclaration:
+ parentSymbol: Symbol = self.env.temp_data['c:parent_symbol']
+
+ max_len = (self.env.config.c_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['c: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['c:last_symbol']
+ if symbol.siblingAbove is not None:
+ assert symbol.siblingAbove.siblingBelow is None
+ symbol.siblingAbove.siblingBelow = symbol
+ self.env.temp_data['c: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['c:last_symbol'] = e.symbol
+ msg = __("Duplicate C declaration, also defined at %s:%s.\n"
+ "Declaration is '.. c:%s:: %s'.")
+ msg = msg % (e.symbol.docname, e.symbol.line, self.display_object_type, sig)
+ logger.warning(msg, location=signode)
+
+ if ast.objectType == 'enumerator':
+ 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)
+ self.describe_signature(signode, ast, options)
+ return ast
+
+ def before_content(self) -> None:
+ lastSymbol: Symbol = self.env.temp_data['c:last_symbol']
+ assert lastSymbol
+ self.oldParentSymbol = self.env.temp_data['c:parent_symbol']
+ self.oldParentKey: LookupKey = self.env.ref_context['c:parent_key']
+ self.env.temp_data['c:parent_symbol'] = lastSymbol
+ self.env.ref_context['c:parent_key'] = lastSymbol.get_lookup_key()
+
+ def after_content(self) -> None:
+ self.env.temp_data['c:parent_symbol'] = self.oldParentSymbol
+ self.env.ref_context['c:parent_key'] = self.oldParentKey
+
+
+class CMemberObject(CObject):
+ object_type = 'member'
+
+ @property
+ def display_object_type(self) -> str:
+ # the distinction between var and member is only cosmetic
+ assert self.objtype in ('member', 'var')
+ return self.objtype
+
+
+_function_doc_field_types = [
+ TypedField('parameter', label=_('Parameters'),
+ names=('param', 'parameter', 'arg', 'argument'),
+ typerolename='expr', typenames=('type',)),
+ GroupedField('retval', label=_('Return values'),
+ names=('retvals', 'retval'),
+ can_collapse=True),
+ Field('returnvalue', label=_('Returns'), has_arg=False,
+ names=('returns', 'return')),
+ Field('returntype', label=_('Return type'), has_arg=False,
+ names=('rtype',)),
+]
+
+
+class CFunctionObject(CObject):
+ object_type = 'function'
+
+ doc_field_types = _function_doc_field_types.copy()
+
+
+class CMacroObject(CObject):
+ object_type = 'macro'
+
+ doc_field_types = _function_doc_field_types.copy()
+
+
+class CStructObject(CObject):
+ object_type = 'struct'
+
+
+class CUnionObject(CObject):
+ object_type = 'union'
+
+
+class CEnumObject(CObject):
+ object_type = 'enum'
+
+
+class CEnumeratorObject(CObject):
+ object_type = 'enumerator'
+
+
+class CTypeObject(CObject):
+ object_type = 'type'
+
+
+class CNamespaceObject(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: ClassVar[OptionSpec] = {}
+
+ def run(self) -> list[Node]:
+ rootSymbol = self.env.domaindata['c']['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.env.config)
+ try:
+ name = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_location())
+ name = _make_phony_error_name()
+ symbol = rootSymbol.add_name(name)
+ stack = [symbol]
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CNamespacePushObject(SphinxDirective):
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec: ClassVar[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.env.config)
+ try:
+ name = parser.parse_namespace_object()
+ parser.assert_end()
+ except DefinitionError as e:
+ logger.warning(e, location=self.get_location())
+ name = _make_phony_error_name()
+ oldParent = self.env.temp_data.get('c:parent_symbol', None)
+ if not oldParent:
+ oldParent = self.env.domaindata['c']['root_symbol']
+ symbol = oldParent.add_name(name)
+ stack = self.env.temp_data.get('c:namespace_stack', [])
+ stack.append(symbol)
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class CNamespacePopObject(SphinxDirective):
+ has_content = False
+ required_arguments = 0
+ optional_arguments = 0
+ final_argument_whitespace = True
+ option_spec: ClassVar[OptionSpec] = {}
+
+ def run(self) -> list[Node]:
+ stack = self.env.temp_data.get('c: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['c']['root_symbol']
+ self.env.temp_data['c:parent_symbol'] = symbol
+ self.env.temp_data['c:namespace_stack'] = stack
+ self.env.ref_context['c:parent_key'] = symbol.get_lookup_key()
+ return []
+
+
+class AliasNode(nodes.Element):
+ def __init__(
+ self,
+ sig: str,
+ aliasOptions: dict,
+ document: Any,
+ env: BuildEnvironment | None = None,
+ parentKey: LookupKey | None = None,
+ ) -> None:
+ super().__init__()
+ self.sig = sig
+ self.aliasOptions = aliasOptions
+ self.document = document
+ if env is not None:
+ if 'c:parent_symbol' not in env.temp_data:
+ root = env.domaindata['c']['root_symbol']
+ env.temp_data['c:parent_symbol'] = root
+ env.ref_context['c:parent_key'] = root.get_lookup_key()
+ self.parentKey = env.ref_context['c:parent_key']
+ else:
+ assert parentKey is not None
+ self.parentKey = parentKey
+
+ def copy(self) -> AliasNode:
+ return self.__class__(self.sig, self.aliasOptions, self.document,
+ 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'] = 'c'
+ # '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
+ 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)
+ name = parser.parse_xref_object()
+ except DefinitionError as e:
+ logger.warning(e, location=node)
+ name = None
+
+ if name 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['c'].data['root_symbol']
+ parentSymbol: Symbol | None = 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
+
+ s = parentSymbol.find_declaration(
+ name, 'any',
+ matchSelf=True, recurseInAnon=True)
+ if s is None:
+ signode = addnodes.desc_signature(sig, '')
+ node.append(signode)
+ signode.clear()
+ signode += addnodes.desc_name(sig, sig)
+
+ logger.warning("Could not find C declaration for alias '%s'." % name,
+ location=node)
+ node.replace_self(signode)
+ continue
+ # Declarations like .. var:: int Missing::var
+ # may introduce symbols without declarations.
+ # But if we skip the root then it is ok to start recursion from it.
+ if not node.aliasOptions['noroot'] and s.declaration is None:
+ signode = addnodes.desc_signature(sig, '')
+ node.append(signode)
+ signode.clear()
+ signode += addnodes.desc_name(sig, sig)
+
+ logger.warning(
+ "Can not render C declaration for alias '%s'. No such declaration." % name,
+ location=node)
+ node.replace_self(signode)
+ continue
+
+ nodes = self._render_symbol(s, maxdepth=node.aliasOptions['maxdepth'],
+ skipThis=node.aliasOptions['noroot'],
+ aliasOptions=node.aliasOptions,
+ renderOptions={}, document=node.document)
+ node.replace_self(nodes)
+
+
+class CAliasObject(ObjectDescription):
+ option_spec: ClassVar[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 if 'noroot'.
+ 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
+ node['no-index'] = True
+
+ 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())
+ for sig in self.get_signatures():
+ node.append(AliasNode(sig, aliasOptions, self.state.document, env=self.env))
+ return [node]
+
+
+class CXRefRole(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 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[0:1] == '~':
+ title = title[1:]
+ dot = title.rfind('.')
+ if dot != -1:
+ title = title[dot + 1:]
+ return title, target
+
+
+class CExprRole(SphinxRole):
+ def __init__(self, asCode: bool) -> None:
+ super().__init__()
+ if asCode:
+ # render the expression as inline code
+ self.class_type = 'c-expr'
+ else:
+ # render the expression as inline text
+ self.class_type = 'c-texpr'
+
+ def run(self) -> tuple[list[Node], list[system_message]]:
+ text = self.text.replace('\n', ' ')
+ parser = DefinitionParser(text, location=self.get_location(),
+ config=self.env.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('c', text, text, classes=[self.class_type])], []
+ parentSymbol = self.env.temp_data.get('c:parent_symbol', None)
+ if parentSymbol is None:
+ parentSymbol = self.env.domaindata['c']['root_symbol']
+ # ...most if not all of these classes should really apply to the individual references,
+ # not the container node
+ signode = addnodes.desc_inline('c', classes=[self.class_type])
+ ast.describe_signature(signode, 'markType', self.env, parentSymbol)
+ return [signode], []
+
+
+class CDomain(Domain):
+ """C language domain."""
+
+ name = 'c'
+ label = 'C'
+ object_types = {
+ # 'identifier' is the one used for xrefs generated in signatures, not in roles
+ 'member': ObjType(_('member'), 'var', 'member', 'data', 'identifier'),
+ 'var': ObjType(_('variable'), 'var', 'member', 'data', 'identifier'),
+ 'function': ObjType(_('function'), 'func', 'identifier', 'type'),
+ 'macro': ObjType(_('macro'), 'macro', 'identifier'),
+ 'struct': ObjType(_('struct'), 'struct', 'identifier', 'type'),
+ 'union': ObjType(_('union'), 'union', 'identifier', 'type'),
+ 'enum': ObjType(_('enum'), 'enum', 'identifier', 'type'),
+ 'enumerator': ObjType(_('enumerator'), 'enumerator', 'identifier'),
+ 'type': ObjType(_('type'), 'identifier', 'type'),
+ # generated object types
+ 'functionParam': ObjType(_('function parameter'), 'identifier', 'var', 'member', 'data'), # NoQA: E501
+ }
+
+ directives = {
+ 'member': CMemberObject,
+ 'var': CMemberObject,
+ 'function': CFunctionObject,
+ 'macro': CMacroObject,
+ 'struct': CStructObject,
+ 'union': CUnionObject,
+ 'enum': CEnumObject,
+ 'enumerator': CEnumeratorObject,
+ 'type': CTypeObject,
+ # scope control
+ 'namespace': CNamespaceObject,
+ 'namespace-push': CNamespacePushObject,
+ 'namespace-pop': CNamespacePopObject,
+ # other
+ 'alias': CAliasObject,
+ }
+ roles = {
+ 'member': CXRefRole(),
+ 'data': CXRefRole(),
+ 'var': CXRefRole(),
+ 'func': CXRefRole(fix_parens=True),
+ 'macro': CXRefRole(),
+ 'struct': CXRefRole(),
+ 'union': CXRefRole(),
+ 'enum': CXRefRole(),
+ 'enumerator': CXRefRole(),
+ 'type': CXRefRole(),
+ 'expr': CExprRole(asCode=True),
+ 'texpr': CExprRole(asCode=False),
+ }
+ initial_data: dict[str, Symbol | dict[str, tuple[str, str, str]]] = {
+ 'root_symbol': Symbol(None, None, None, None, None),
+ 'objects': {}, # fullname -> docname, node_id, objtype
+ }
+
+ 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)
+
+ 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[str, Any]) -> 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")
+ logger.debug("merge_domaindata end")
+
+ self.data['root_symbol'].merge_with(otherdata['root_symbol'],
+ docnames, self.env)
+ ourObjects = self.data['objects']
+ for fullname, (fn, id_, objtype) in otherdata['objects'].items():
+ if fn in docnames:
+ if fullname not in ourObjects:
+ ourObjects[fullname] = (fn, id_, objtype)
+ # no need to warn on duplicates, the symbol merge already does that
+
+ 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]:
+ parser = DefinitionParser(target, location=node, config=env.config)
+ try:
+ name = parser.parse_xref_object()
+ except DefinitionError as e:
+ logger.warning('Unparseable C cross-reference: %r\n%s', target, e,
+ location=node)
+ return None, None
+ parentKey: LookupKey = node.get("c: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)
+ logger.debug(rootSymbol.dump(1))
+ assert parentSymbol # should be there
+ else:
+ parentSymbol = rootSymbol
+ s = parentSymbol.find_declaration(name, typ,
+ matchSelf=True, recurseInAnon=True)
+ if s is None or s.declaration is None:
+ return None, None
+
+ # TODO: check role type vs. object type
+
+ declaration = s.declaration
+ displayName = name.get_display_string()
+ docname = s.docname
+ assert docname
+
+ return make_refnode(builder, fromdocname, docname,
+ declaration.get_newest_id(), contnode, displayName,
+ ), declaration.objectType
+
+ 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:
+ return [('c:' + 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 setup(app: Sphinx) -> ExtensionMetadata:
+ app.add_domain(CDomain)
+ app.add_config_value("c_id_attributes", [], 'env')
+ app.add_config_value("c_paren_attributes", [], 'env')
+ app.add_config_value("c_extra_keywords", _macroKeywords, 'env')
+ app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None})
+ app.add_post_transform(AliasTransform)
+
+ return {
+ 'version': 'builtin',
+ 'env_version': 3,
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/domains/c/_ast.py b/sphinx/domains/c/_ast.py
new file mode 100644
index 0000000..3a8e2a2
--- /dev/null
+++ b/sphinx/domains/c/_ast.py
@@ -0,0 +1,1421 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Union, cast
+
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.domains.c._ids import _id_prefix, _max_id
+from sphinx.util.cfamily import (
+ ASTAttributeList,
+ ASTBaseBase,
+ ASTBaseParenExprList,
+ StringifyTransform,
+ UnsupportedMultiCharacterCharLiteral,
+ verify_description_mode,
+)
+
+if TYPE_CHECKING:
+
+ from docutils.nodes import Element, Node, TextElement
+
+ from sphinx.domains.c._symbol import Symbol
+ from sphinx.environment import BuildEnvironment
+
+DeclarationType = Union[
+ "ASTStruct", "ASTUnion", "ASTEnum", "ASTEnumerator",
+ "ASTType", "ASTTypeWithInit", "ASTMacro",
+]
+
+
+class ASTBase(ASTBaseBase):
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ raise NotImplementedError(repr(self))
+
+
+# Names
+################################################################################
+
+class ASTIdentifier(ASTBaseBase):
+ def __init__(self, identifier: str) -> None:
+ assert identifier is not None
+ assert len(identifier) != 0
+ self.identifier = identifier
+
+ # ASTBaseBase already implements this method,
+ # but specialising it here improves performance
+ def __eq__(self, other: object) -> bool:
+ if type(other) is not ASTIdentifier:
+ return NotImplemented
+ return self.identifier == other.identifier
+
+ def is_anon(self) -> bool:
+ return self.identifier[0] == '@'
+
+ # 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, symbol: Symbol) -> None:
+ # note: slightly different signature of describe_signature due to the prefix
+ 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
+ pnode = addnodes.pending_xref('', refdomain='c',
+ reftype='identifier',
+ reftarget=targetText, modname=None,
+ classname=None)
+ pnode['c: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
+ else:
+ raise Exception('Unknown description mode: %s' % mode)
+
+
+class ASTNestedName(ASTBase):
+ def __init__(self, names: list[ASTIdentifier], rooted: bool) -> None:
+ assert len(names) > 0
+ self.names = names
+ self.rooted = rooted
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self
+
+ def get_id(self, version: int) -> str:
+ return '.'.join(str(n) for n in self.names)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = '.'.join(transform(n) for n in self.names)
+ if self.rooted:
+ return '.' + res
+ else:
+ return 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('.')
+ n = self.names[i]
+ n.describe_signature(signode, mode, env, '', symbol)
+ elif mode == 'param':
+ assert not self.rooted, str(self)
+ assert len(self.names) == 1
+ self.names[0].describe_signature(signode, 'noneIsName', env, '', symbol)
+ elif mode in ('markType', 'lastIsName', 'markName'):
+ # Each element should be a pending xref targeting the complete
+ # prefix.
+ 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.
+ # TODO: also for C?
+ # 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)):
+ ident = names[i]
+ if not first:
+ dest += addnodes.desc_sig_punctuation('.', '.')
+ prefix += '.'
+ first = False
+ txt_ident = str(ident)
+ if txt_ident != '':
+ ident.describe_signature(dest, 'markType', env, prefix, symbol)
+ prefix += txt_ident
+ if mode == 'lastIsName':
+ if len(self.names) > 1:
+ dest += addnodes.desc_sig_punctuation('.', '.')
+ signode += dest
+ self.names[-1].describe_signature(signode, mode, env, '', symbol)
+ else:
+ raise Exception('Unknown description mode: %s' % mode)
+
+
+################################################################################
+# Expressions
+################################################################################
+
+class ASTExpression(ASTBase):
+ pass
+
+
+# Primary expressions
+################################################################################
+
+class ASTLiteral(ASTExpression):
+ pass
+
+
+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 describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ txt = str(self)
+ signode += addnodes.desc_sig_keyword(txt, txt)
+
+
+class ASTNumberLiteral(ASTLiteral):
+ def __init__(self, data: str) -> None:
+ self.data = data
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.data
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ txt = str(self)
+ signode += addnodes.desc_sig_literal_number(txt, txt)
+
+
+class ASTCharLiteral(ASTLiteral):
+ def __init__(self, prefix: str, data: str) -> None:
+ self.prefix = prefix # may be None when no prefix
+ self.data = data
+ 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 describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ txt = str(self)
+ signode += addnodes.desc_sig_literal_char(txt, txt)
+
+
+class ASTStringLiteral(ASTLiteral):
+ def __init__(self, data: str) -> None:
+ self.data = data
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return self.data
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ txt = str(self)
+ signode += addnodes.desc_sig_literal_string(txt, txt)
+
+
+class ASTIdExpression(ASTExpression):
+ def __init__(self, name: ASTNestedName) -> None:
+ # 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)
+
+
+class ASTParenExpr(ASTExpression):
+ def __init__(self, expr: ASTExpression) -> None:
+ 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) # type: ignore[attr-defined]
+
+ 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(')', ')')
+
+
+# Postfix expressions
+################################################################################
+
+class ASTPostfixOp(ASTBase):
+ pass
+
+
+class ASTPostfixCallExpr(ASTPostfixOp):
+ def __init__(self, lst: ASTParenExprList | ASTBracedInitList) -> None:
+ self.lst = lst
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return transform(self.lst)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ self.lst.describe_signature(signode, mode, env, symbol)
+
+
+class ASTPostfixArray(ASTPostfixOp):
+ def __init__(self, expr: ASTExpression) -> None:
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '[' + transform(self.expr) + ']'
+
+ 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 ASTPostfixInc(ASTPostfixOp):
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '++'
+
+ 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 describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ signode += addnodes.desc_sig_operator('--', '--')
+
+
+class ASTPostfixMemberOfPointer(ASTPostfixOp):
+ def __init__(self, name: ASTNestedName) -> None:
+ self.name = name
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return '->' + transform(self.name)
+
+ 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 ASTPostfixExpr(ASTExpression):
+ def __init__(self, prefix: ASTExpression, postFixes: list[ASTPostfixOp]) -> None:
+ self.prefix = prefix
+ self.postFixes = postFixes
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ''.join([transform(self.prefix), *(transform(p) for p in self.postFixes)])
+
+ 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)
+
+
+# Unary expressions
+################################################################################
+
+class ASTUnaryOpExpr(ASTExpression):
+ def __init__(self, op: str, expr: ASTExpression) -> None:
+ 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 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 ASTSizeofType(ASTExpression):
+ def __init__(self, typ: ASTType) -> None:
+ self.typ = typ
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "sizeof(" + transform(self.typ) + ")"
+
+ 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) -> None:
+ self.expr = expr
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "sizeof " + transform(self.expr)
+
+ 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) -> None:
+ self.typ = typ
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return "alignof(" + transform(self.typ) + ")"
+
+ 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(')', ')')
+
+
+# Other expressions
+################################################################################
+
+class ASTCastExpr(ASTExpression):
+ def __init__(self, typ: ASTType, expr: ASTExpression) -> None:
+ 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 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(ASTBase):
+ def __init__(self, exprs: list[ASTExpression], ops: list[str]) -> None:
+ 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 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 ASTAssignmentExpr(ASTExpression):
+ def __init__(self, exprs: list[ASTExpression], ops: list[str]) -> None:
+ 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 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 ASTFallbackExpr(ASTExpression):
+ def __init__(self, expr: str) -> None:
+ 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
+################################################################################
+
+class ASTTrailingTypeSpec(ASTBase):
+ pass
+
+
+class ASTTrailingTypeSpecFundamental(ASTTrailingTypeSpec):
+ def __init__(self, names: list[str]) -> None:
+ assert len(names) != 0
+ self.names = names
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ' '.join(self.names)
+
+ 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 ASTTrailingTypeSpecName(ASTTrailingTypeSpec):
+ def __init__(self, prefix: str, nestedName: ASTNestedName) -> None:
+ self.prefix = prefix
+ self.nestedName = nestedName
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.nestedName
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.prefix:
+ res.append(self.prefix)
+ res.append(' ')
+ res.append(transform(self.nestedName))
+ 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)
+
+
+class ASTFunctionParameter(ASTBase):
+ def __init__(self, arg: ASTTypeWithInit | None, ellipsis: bool = False) -> None:
+ self.arg = arg
+ self.ellipsis = ellipsis
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ # the anchor will be our parent
+ return symbol.parent.declaration.get_id(version, prefixed=False)
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.ellipsis:
+ return '...'
+ else:
+ return transform(self.arg)
+
+ def describe_signature(self, signode: Any, 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 ASTParameters(ASTBase):
+ def __init__(self, args: list[ASTFunctionParameter], attrs: ASTAttributeList) -> None:
+ self.args = args
+ self.attrs = attrs
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return 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 len(self.attrs) != 0:
+ res.append(' ')
+ res.append(transform(self.attrs))
+ 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(')', ')')
+
+ if len(self.attrs) != 0:
+ signode += addnodes.desc_sig_space()
+ self.attrs.describe_signature(signode)
+
+
+class ASTDeclSpecsSimple(ASTBaseBase):
+ def __init__(self, storage: str, threadLocal: str, inline: bool,
+ restrict: bool, volatile: bool, const: bool, attrs: ASTAttributeList) -> None:
+ self.storage = storage
+ self.threadLocal = threadLocal
+ self.inline = inline
+ self.restrict = restrict
+ self.volatile = volatile
+ self.const = const
+ 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.volatile or other.volatile,
+ self.const or other.const,
+ self.restrict or other.restrict,
+ 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(self.threadLocal)
+ if self.inline:
+ res.append('inline')
+ if self.restrict:
+ res.append('restrict')
+ if self.volatile:
+ res.append('volatile')
+ if self.const:
+ res.append('const')
+ return ' '.join(res)
+
+ def describe_signature(self, modifiers: list[Node]) -> None:
+ def _add(modifiers: list[Node], text: str) -> None:
+ if len(modifiers) != 0:
+ modifiers.append(addnodes.desc_sig_space())
+ modifiers.append(addnodes.desc_sig_keyword(text, text))
+
+ if len(modifiers) != 0 and len(self.attrs) != 0:
+ modifiers.append(addnodes.desc_sig_space())
+ tempNode = nodes.TextElement()
+ self.attrs.describe_signature(tempNode)
+ modifiers.extend(tempNode.children)
+ if self.storage:
+ _add(modifiers, self.storage)
+ if self.threadLocal:
+ _add(modifiers, self.threadLocal)
+ if self.inline:
+ _add(modifiers, 'inline')
+ if self.restrict:
+ _add(modifiers, 'restrict')
+ if self.volatile:
+ _add(modifiers, 'volatile')
+ if self.const:
+ _add(modifiers, '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 TODO: remove?
+ self.outer = outer
+ self.leftSpecs = leftSpecs
+ self.rightSpecs = rightSpecs
+ self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
+ self.trailingTypeSpec = trailing
+
+ 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)
+ modifiers: list[Node] = []
+
+ self.leftSpecs.describe_signature(modifiers)
+
+ for m in modifiers:
+ signode += m
+ if self.trailingTypeSpec:
+ if len(modifiers) > 0:
+ signode += addnodes.desc_sig_space()
+ self.trailingTypeSpec.describe_signature(signode, mode, env,
+ symbol=symbol)
+ modifiers = []
+ self.rightSpecs.describe_signature(modifiers)
+ if len(modifiers) > 0:
+ signode += addnodes.desc_sig_space()
+ for m in modifiers:
+ signode += m
+
+
+# Declarator
+################################################################################
+
+class ASTArray(ASTBase):
+ def __init__(self, static: bool, const: bool, volatile: bool, restrict: bool,
+ vla: bool, size: ASTExpression) -> None:
+ self.static = static
+ self.const = const
+ self.volatile = volatile
+ self.restrict = restrict
+ self.vla = vla
+ self.size = size
+ if vla:
+ assert size is None
+ if size is not None:
+ assert not vla
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ el = []
+ if self.static:
+ el.append('static')
+ if self.restrict:
+ el.append('restrict')
+ if self.volatile:
+ el.append('volatile')
+ if self.const:
+ el.append('const')
+ if self.vla:
+ return '[' + ' '.join(el) + '*]'
+ elif self.size:
+ el.append(transform(self.size))
+ return '[' + ' '.join(el) + ']'
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ signode += addnodes.desc_sig_punctuation('[', '[')
+ addSpace = False
+
+ 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.static:
+ addSpace = _add(signode, 'static')
+ if self.restrict:
+ addSpace = _add(signode, 'restrict')
+ if self.volatile:
+ addSpace = _add(signode, 'volatile')
+ if self.const:
+ addSpace = _add(signode, 'const')
+ if self.vla:
+ signode += addnodes.desc_sig_punctuation('*', '*')
+ elif self.size:
+ if addSpace:
+ signode += addnodes.desc_sig_space()
+ 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))
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ raise NotImplementedError(repr(self))
+
+ def require_space_after_declSpecs(self) -> bool:
+ raise NotImplementedError(repr(self))
+
+
+class ASTDeclaratorNameParam(ASTDeclarator):
+ def __init__(self, declId: ASTNestedName,
+ arrayOps: list[ASTArray], param: ASTParameters) -> None:
+ self.declId = declId
+ self.arrayOps = arrayOps
+ self.param = param
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.declId
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.param.function_params
+
+ # ------------------------------------------------------------------------
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.declId is not None
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = []
+ if self.declId:
+ res.append(transform(self.declId))
+ res.extend(transform(op) for op in self.arrayOps)
+ if self.param:
+ res.append(transform(self.param))
+ 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.param:
+ self.param.describe_signature(signode, mode, env, symbol)
+
+
+class ASTDeclaratorNameBitField(ASTDeclarator):
+ def __init__(self, declId: ASTNestedName, size: ASTExpression) -> None:
+ self.declId = declId
+ self.size = size
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.declId
+
+ # ------------------------------------------------------------------------
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.declId is not None
+
+ 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, restrict: bool, volatile: bool, const: bool,
+ attrs: ASTAttributeList) -> None:
+ assert next
+ self.next = next
+ self.restrict = restrict
+ self.volatile = volatile
+ self.const = const
+ self.attrs = attrs
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.next.name
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.next.function_params
+
+ def require_space_after_declSpecs(self) -> bool:
+ return self.const or self.volatile or self.restrict or \
+ len(self.attrs) > 0 or \
+ 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.restrict or self.volatile or self.const):
+ res.append(' ')
+ if self.restrict:
+ res.append('restrict')
+ if self.volatile:
+ if self.restrict:
+ res.append(' ')
+ res.append('volatile')
+ if self.const:
+ if self.restrict or self.volatile:
+ res.append(' ')
+ res.append('const')
+ if self.const or self.volatile or self.restrict or len(self.attrs) > 0:
+ if self.next.require_space_after_declSpecs():
+ res.append(' ')
+ res.append(transform(self.next))
+ return ''.join(res)
+
+ 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.restrict or 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.restrict:
+ _add_anno(signode, 'restrict')
+ if self.volatile:
+ if self.restrict:
+ signode += addnodes.desc_sig_space()
+ _add_anno(signode, 'volatile')
+ if self.const:
+ if self.restrict or self.volatile:
+ signode += addnodes.desc_sig_space()
+ _add_anno(signode, 'const')
+ if self.const or self.volatile or self.restrict 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 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 and params are in inner
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.inner.name
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.inner.function_params
+
+ 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 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)
+
+
+# Initializer
+################################################################################
+
+class ASTParenExprList(ASTBaseParenExprList):
+ def __init__(self, exprs: list[ASTExpression]) -> None:
+ self.exprs = 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 ASTBracedInitList(ASTBase):
+ def __init__(self, exprs: list[ASTExpression], trailingComma: bool) -> None:
+ self.exprs = exprs
+ self.trailingComma = trailingComma
+
+ 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 ASTInitializer(ASTBase):
+ def __init__(self, value: ASTBracedInitList | ASTExpression,
+ 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
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ return symbol.get_full_nested_name().get_id(version)
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter]:
+ return self.decl.function_params
+
+ 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 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
+
+ def get_id(self, version: int, objectType: str, symbol: Symbol) -> str:
+ return self.type.get_id(version, objectType, symbol)
+
+ 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 ASTMacroParameter(ASTBase):
+ def __init__(self, arg: ASTNestedName | None, ellipsis: bool = False,
+ variadic: bool = False) -> None:
+ self.arg = arg
+ self.ellipsis = ellipsis
+ self.variadic = variadic
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ if self.ellipsis:
+ return '...'
+ elif self.variadic:
+ return transform(self.arg) + '...'
+ else:
+ return transform(self.arg)
+
+ def describe_signature(self, signode: Any, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ if self.ellipsis:
+ signode += addnodes.desc_sig_punctuation('...', '...')
+ elif self.variadic:
+ name = str(self)
+ signode += addnodes.desc_sig_name(name, name)
+ else:
+ self.arg.describe_signature(signode, mode, env, symbol=symbol)
+
+
+class ASTMacro(ASTBase):
+ def __init__(self, ident: ASTNestedName, args: list[ASTMacroParameter] | None) -> None:
+ self.ident = ident
+ self.args = args
+
+ @property
+ def name(self) -> ASTNestedName:
+ return self.ident
+
+ 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.ident))
+ if self.args is not None:
+ res.append('(')
+ first = True
+ for arg in self.args:
+ if not first:
+ res.append(', ')
+ first = False
+ res.append(transform(arg))
+ res.append(')')
+ return ''.join(res)
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, symbol: Symbol) -> None:
+ verify_description_mode(mode)
+ self.ident.describe_signature(signode, mode, env, symbol)
+ if self.args is None:
+ return
+ paramlist = addnodes.desc_parameterlist()
+ for arg in self.args:
+ param = addnodes.desc_parameter('', '', noemph=True)
+ arg.describe_signature(param, 'param', env, symbol=symbol)
+ paramlist += param
+ signode += paramlist
+
+
+class ASTStruct(ASTBase):
+ def __init__(self, name: ASTNestedName) -> None:
+ self.name = name
+
+ 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:
+ return transform(self.name)
+
+ 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)
+
+
+class ASTUnion(ASTBase):
+ def __init__(self, name: ASTNestedName) -> None:
+ self.name = name
+
+ 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:
+ return transform(self.name)
+
+ 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)
+
+
+class ASTEnum(ASTBase):
+ def __init__(self, name: ASTNestedName) -> None:
+ self.name = name
+
+ 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:
+ return transform(self.name)
+
+ 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)
+
+
+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:
+ 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)
+
+
+class ASTDeclaration(ASTBaseBase):
+ def __init__(self, objectType: str, directiveType: str | None,
+ declaration: DeclarationType | ASTFunctionParameter,
+ semicolon: bool = False) -> None:
+ self.objectType = objectType
+ self.directiveType = directiveType
+ self.declaration = declaration
+ self.semicolon = semicolon
+
+ self.symbol: Symbol | None = None
+ # set by CObject._add_enumerator_to_parent
+ self.enumeratorScopedSymbol: Symbol | None = None
+
+ # the cache assumes that by the time get_newest_id is called, no
+ # further changes will be made to this object
+ self._newest_id_cache: str | None = None
+
+ def clone(self) -> ASTDeclaration:
+ return ASTDeclaration(self.objectType, self.directiveType,
+ self.declaration.clone(), self.semicolon)
+
+ @property
+ def name(self) -> ASTNestedName:
+ decl = cast(DeclarationType, self.declaration)
+ return decl.name
+
+ @property
+ def function_params(self) -> list[ASTFunctionParameter] | None:
+ if self.objectType != 'function':
+ return None
+ decl = cast(ASTType, self.declaration)
+ return decl.function_params
+
+ def get_id(self, version: int, prefixed: bool = True) -> str:
+ if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
+ return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
+ id_ = self.declaration.get_id(version, self.objectType, self.symbol)
+ if prefixed:
+ return _id_prefix[version] + id_
+ else:
+ return id_
+
+ def get_newest_id(self) -> str:
+ if self._newest_id_cache is None:
+ self._newest_id_cache = self.get_id(_max_id, True)
+ return self._newest_id_cache
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ res = transform(self.declaration)
+ if self.semicolon:
+ res += ';'
+ return res
+
+ def describe_signature(self, signode: TextElement, mode: str,
+ env: BuildEnvironment, options: dict[str, bool]) -> 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
+ signode += mainDeclNode
+
+ if self.objectType in {'member', 'function', 'macro'}:
+ pass
+ elif self.objectType == 'struct':
+ mainDeclNode += addnodes.desc_sig_keyword('struct', 'struct')
+ 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()
+ elif self.objectType == 'enumerator':
+ mainDeclNode += addnodes.desc_sig_keyword('enumerator', 'enumerator')
+ mainDeclNode += addnodes.desc_sig_space()
+ elif self.objectType == 'type':
+ decl = cast(ASTType, self.declaration)
+ prefix = decl.get_type_declaration_prefix()
+ mainDeclNode += addnodes.desc_sig_keyword(prefix, prefix)
+ mainDeclNode += addnodes.desc_sig_space()
+ else:
+ raise AssertionError
+ self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
+ if self.semicolon:
+ mainDeclNode += addnodes.desc_sig_punctuation(';', ';')
diff --git a/sphinx/domains/c/_ids.py b/sphinx/domains/c/_ids.py
new file mode 100644
index 0000000..cd617be
--- /dev/null
+++ b/sphinx/domains/c/_ids.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+import re
+
+# https://en.cppreference.com/w/c/keyword
+_keywords = [
+ 'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do', 'double',
+ 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if', 'inline', 'int', 'long',
+ 'register', 'restrict', 'return', 'short', 'signed', 'sizeof', 'static', 'struct',
+ 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
+ '_Alignas', '_Alignof', '_Atomic', '_Bool', '_Complex',
+ '_Decimal32', '_Decimal64', '_Decimal128',
+ '_Generic', '_Imaginary', '_Noreturn', '_Static_assert', '_Thread_local',
+]
+# These are only keyword'y when the corresponding headers are included.
+# They are used as default value for c_extra_keywords.
+_macroKeywords = [
+ 'alignas', 'alignof', 'bool', 'complex', 'imaginary', 'noreturn', 'static_assert',
+ 'thread_local',
+]
+
+# these are ordered by precedence
+_expression_bin_ops = [
+ ['||', 'or'],
+ ['&&', 'and'],
+ ['|', 'bitor'],
+ ['^', 'xor'],
+ ['&', 'bitand'],
+ ['==', '!=', 'not_eq'],
+ ['<=', '>=', '<', '>'],
+ ['<<', '>>'],
+ ['+', '-'],
+ ['*', '/', '%'],
+ ['.*', '->*'],
+]
+_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"]
+_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
+ ">>=", "<<=", "&=", "and_eq", "^=", "xor_eq", "|=", "or_eq"]
+
+_max_id = 1
+_id_prefix = [None, 'c.', 'Cv2.']
+# Ids are used in lookup keys which are used across pickled files,
+# so when _max_id changes, make sure to update the ENV_VERSION.
+
+_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
+ r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.DOTALL)
+
+# bool, complex, and imaginary are macro "keywords", so they are handled separately
+_simple_type_specifiers_re = re.compile(r"""
+ \b(
+ void|_Bool
+ |signed|unsigned
+ |short|long
+ |char
+ |int
+ |__uint128|__int128
+ |__int(8|16|32|64|128) # extension
+ |float|double
+ |_Decimal(32|64|128)
+ |_Complex|_Imaginary
+ |__float80|_Float64x|__float128|_Float128|__ibm128 # extension
+ |__fp16 # extension
+ |_Sat|_Fract|fract|_Accum|accum # extension
+ )\b
+""", re.VERBOSE)
diff --git a/sphinx/domains/c/_parser.py b/sphinx/domains/c/_parser.py
new file mode 100644
index 0000000..8c65825
--- /dev/null
+++ b/sphinx/domains/c/_parser.py
@@ -0,0 +1,1048 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Callable
+
+from sphinx.domains.c._ast import (
+ ASTAlignofExpr,
+ ASTArray,
+ ASTAssignmentExpr,
+ ASTBinOpExpr,
+ ASTBooleanLiteral,
+ ASTBracedInitList,
+ ASTCastExpr,
+ ASTCharLiteral,
+ ASTDeclaration,
+ ASTDeclarator,
+ ASTDeclaratorNameBitField,
+ ASTDeclaratorNameParam,
+ ASTDeclaratorParen,
+ ASTDeclaratorPtr,
+ ASTDeclSpecs,
+ ASTDeclSpecsSimple,
+ ASTEnum,
+ ASTEnumerator,
+ ASTExpression,
+ ASTFallbackExpr,
+ ASTFunctionParameter,
+ ASTIdentifier,
+ ASTIdExpression,
+ ASTInitializer,
+ ASTLiteral,
+ ASTMacro,
+ ASTMacroParameter,
+ ASTNestedName,
+ ASTNumberLiteral,
+ ASTParameters,
+ ASTParenExpr,
+ ASTParenExprList,
+ ASTPostfixArray,
+ ASTPostfixCallExpr,
+ ASTPostfixDec,
+ ASTPostfixExpr,
+ ASTPostfixInc,
+ ASTPostfixMemberOfPointer,
+ ASTPostfixOp,
+ ASTSizeofExpr,
+ ASTSizeofType,
+ ASTStringLiteral,
+ ASTStruct,
+ ASTTrailingTypeSpec,
+ ASTTrailingTypeSpecFundamental,
+ ASTTrailingTypeSpecName,
+ ASTType,
+ ASTTypeWithInit,
+ ASTUnaryOpExpr,
+ ASTUnion,
+ DeclarationType,
+)
+from sphinx.domains.c._ids import (
+ _expression_assignment_ops,
+ _expression_bin_ops,
+ _expression_unary_ops,
+ _keywords,
+ _simple_type_specifiers_re,
+ _string_re,
+)
+from sphinx.util.cfamily import (
+ ASTAttributeList,
+ BaseParser,
+ DefinitionError,
+ UnsupportedMultiCharacterCharLiteral,
+ 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,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+
+class DefinitionParser(BaseParser):
+ @property
+ def language(self) -> str:
+ return 'C'
+
+ @property
+ def id_attributes(self) -> Sequence[str]:
+ return self.config.c_id_attributes
+
+ @property
+ def paren_attributes(self) -> Sequence[str]:
+ return self.config.c_paren_attributes
+
+ def _parse_string(self) -> str | None:
+ 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 | None:
+ # -> integer-literal
+ # | character-literal
+ # | floating-literal
+ # | string-literal
+ # | boolean-literal -> "false" | "true"
+ self.skip_ws()
+ 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):
+ self.match(float_literal_suffix_re)
+ return ASTNumberLiteral(self.definition[pos:self.pos])
+ for regex in (binary_literal_re, hex_literal_re,
+ integer_literal_re, octal_literal_re):
+ if self.match(regex):
+ self.match(integers_literal_suffix_re)
+ return ASTNumberLiteral(self.definition[pos:self.pos])
+
+ string = self._parse_string()
+ if string is not None:
+ return 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:
+ return 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 None
+
+ def _parse_paren_expression(self) -> ASTExpression | None:
+ # "(" expression ")"
+ if self.current_char != '(':
+ return None
+ self.pos += 1
+ res = self._parse_expression()
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail("Expected ')' in end of parenthesized expression.")
+ return ASTParenExpr(res)
+
+ def _parse_primary_expression(self) -> ASTExpression | None:
+ # literal
+ # "(" expression ")"
+ # id-expression -> we parse this with _parse_nested_name
+ self.skip_ws()
+ res: ASTExpression | None = self._parse_literal()
+ if res is not None:
+ return res
+ res = self._parse_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] | None, bool | None]:
+ # Parse open and close with the actual initializer-list in between
+ # -> initializer-clause '...'[opt]
+ # | initializer-list ',' initializer-clause '...'[opt]
+ # TODO: designators
+ self.skip_ws()
+ if not self.skip_string_and_ws(open):
+ return None, None
+ if self.skip_string(close):
+ return [], False
+
+ exprs = []
+ trailingComma = False
+ while True:
+ self.skip_ws()
+ expr = self._parse_expression()
+ self.skip_ws()
+ 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 == '}':
+ self.pos += 1
+ trailingComma = True
+ break
+ return exprs, trailingComma
+
+ def _parse_paren_expression_list(self) -> ASTParenExprList | None:
+ # -> '(' 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_braced_init_list(self) -> ASTBracedInitList | None:
+ # -> '{' initializer-list ','[opt] '}'
+ # | '{' '}'
+ exprs, trailingComma = self._parse_initializer_list("braced-init-list", '{', '}')
+ if exprs is None:
+ return None
+ return ASTBracedInitList(exprs, trailingComma)
+
+ def _parse_postfix_expression(self) -> ASTPostfixExpr:
+ # -> primary
+ # | postfix "[" expression "]"
+ # | postfix "[" braced-init-list [opt] "]"
+ # | postfix "(" expression-list [opt] ")"
+ # | postfix "." id-expression // taken care of in primary by nested name
+ # | postfix "->" id-expression
+ # | postfix "++"
+ # | postfix "--"
+
+ prefix = self._parse_primary_expression()
+
+ # and now parse postfixes
+ postFixes: list[ASTPostfixOp] = []
+ while True:
+ self.skip_ws()
+ 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 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_paren_expression_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 ")"
+ # | "alignof" "(" type-id ")"
+ 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('('):
+ 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)
+ 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) -> 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) -> ASTExpression:
+ if opId + 1 == len(_expression_bin_ops):
+ def parser() -> ASTExpression:
+ return self._parse_cast_expression()
+ else:
+ def parser() -> ASTExpression:
+ return _parse_bin_op_expr(self, opId + 1)
+ exprs = []
+ ops = []
+ exprs.append(parser())
+ while True:
+ self.skip_ws()
+ 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 == self.current_char == '&':
+ # don't split the && 'token'
+ self.pos -= 1
+ # and btw. && has lower precedence, so we are done
+ break
+ try:
+ expr = parser()
+ exprs.append(expr)
+ ops.append(op)
+ oneMore = True
+ break
+ except DefinitionError:
+ self.pos = pos
+ if not oneMore:
+ break
+ return ASTBinOpExpr(exprs, ops) # type: ignore[return-value]
+ return _parse_bin_op_expr(self, 0)
+
+ def _parse_conditional_expression_tail(self, orExprHead: Any) -> ASTExpression | None:
+ # -> "?" expression ":" assignment-expression
+ return None
+
+ def _parse_assignment_expression(self) -> ASTExpression:
+ # -> conditional-expression
+ # | logical-or-expression assignment-operator initializer-clause
+ # -> conditional-expression ->
+ # logical-or-expression
+ # | logical-or-expression "?" expression ":" assignment-expression
+ # | logical-or-expression assignment-operator initializer-clause
+ exprs = []
+ ops = []
+ orExpr = self._parse_logical_or_expression()
+ exprs.append(orExpr)
+ # TODO: handle ternary with _parse_conditional_expression_tail
+ while True:
+ oneMore = False
+ self.skip_ws()
+ for op in _expression_assignment_ops:
+ if op[0] in 'abcnox':
+ if not self.skip_word(op):
+ continue
+ else:
+ if not self.skip_string(op):
+ continue
+ expr = self._parse_logical_or_expression()
+ exprs.append(expr)
+ ops.append(op)
+ oneMore = True
+ if not oneMore:
+ break
+ return ASTAssignmentExpr(exprs, ops)
+
+ def _parse_constant_expression(self) -> ASTExpression:
+ # -> conditional-expression
+ orExpr = self._parse_logical_or_expression()
+ # TODO: use _parse_conditional_expression_tail
+ return orExpr
+
+ def _parse_expression(self) -> ASTExpression:
+ # -> assignment-expression
+ # | expression "," assignment-expression
+ # TODO: actually parse the second production
+ return self._parse_assignment_expression()
+
+ 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_nested_name(self) -> ASTNestedName:
+ names: list[Any] = []
+
+ self.skip_ws()
+ rooted = False
+ if self.skip_string('.'):
+ rooted = True
+ while 1:
+ self.skip_ws()
+ if not self.match(identifier_re):
+ 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)
+ if self.matched_text in self.config.c_extra_keywords:
+ msg = "Expected identifier, got user-defined keyword: %s." \
+ + " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ + "Currently c_extra_keywords is %s."
+ self.fail(msg % (self.matched_text,
+ str(self.config.c_extra_keywords)))
+ ident = ASTIdentifier(identifier)
+ names.append(ident)
+
+ self.skip_ws()
+ if not self.skip_string('.'):
+ break
+ return ASTNestedName(names, rooted)
+
+ def _parse_simple_type_specifier(self) -> str | None:
+ if self.match(_simple_type_specifiers_re):
+ return self.matched_text
+ for t in ('bool', 'complex', 'imaginary'):
+ if t in self.config.c_extra_keywords:
+ if self.skip_word(t):
+ return t
+ return None
+
+ def _parse_simple_type_specifiers(self) -> ASTTrailingTypeSpecFundamental | None:
+ names: list[str] = []
+
+ self.skip_ws()
+ while True:
+ t = self._parse_simple_type_specifier()
+ if t is None:
+ break
+ names.append(t)
+ self.skip_ws()
+ if len(names) == 0:
+ return None
+ return ASTTrailingTypeSpecFundamental(names)
+
+ def _parse_trailing_type_spec(self) -> ASTTrailingTypeSpec:
+ # fundamental types, https://en.cppreference.com/w/c/language/type
+ # and extensions
+ self.skip_ws()
+ res = self._parse_simple_type_specifiers()
+ if res is not None:
+ return res
+
+ # prefixed
+ prefix = None
+ self.skip_ws()
+ for k in ('struct', 'enum', 'union'):
+ if self.skip_word_and_ws(k):
+ prefix = k
+ break
+
+ nestedName = self._parse_nested_name()
+ return ASTTrailingTypeSpecName(prefix, nestedName)
+
+ def _parse_parameters(self, paramMode: str) -> ASTParameters | None:
+ self.skip_ws()
+ if not self.skip_string('('):
+ if paramMode == 'function':
+ self.fail('Expecting "(" in parameters.')
+ 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.')
+ 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(f'Expecting "," or ")" in parameters, got "{self.current_char}".')
+
+ attrs = self._parse_attribute_list()
+ return ASTParameters(args, attrs)
+
+ def _parse_decl_specs_simple(
+ self, outer: str | None, typed: bool,
+ ) -> ASTDeclSpecsSimple:
+ """Just parse the simple ones."""
+ storage = None
+ threadLocal = None
+ inline = None
+ restrict = None
+ volatile = None
+ const = None
+ attrs = []
+ while 1: # accept any permutation of a subset of some decl-specs
+ self.skip_ws()
+ if not storage:
+ if outer == 'member':
+ if self.skip_word('auto'):
+ storage = 'auto'
+ continue
+ if self.skip_word('register'):
+ storage = 'register'
+ continue
+ if outer in ('member', 'function'):
+ if self.skip_word('static'):
+ storage = 'static'
+ continue
+ if self.skip_word('extern'):
+ storage = 'extern'
+ continue
+ if outer == 'member' and not threadLocal:
+ if self.skip_word('thread_local'):
+ threadLocal = 'thread_local'
+ continue
+ if self.skip_word('_Thread_local'):
+ threadLocal = '_Thread_local'
+ continue
+ if outer == 'function' and not inline:
+ inline = self.skip_word('inline')
+ if inline:
+ continue
+
+ if not restrict and typed:
+ restrict = self.skip_word('restrict')
+ if restrict:
+ continue
+ if not volatile and typed:
+ volatile = self.skip_word('volatile')
+ if volatile:
+ continue
+ if not const and typed:
+ const = self.skip_word('const')
+ if const:
+ continue
+ attr = self._parse_attribute()
+ if attr:
+ attrs.append(attr)
+ continue
+ break
+ return ASTDeclSpecsSimple(storage, threadLocal, inline,
+ restrict, volatile, const, ASTAttributeList(attrs))
+
+ def _parse_decl_specs(self, outer: str | None, typed: bool = True) -> ASTDeclSpecs:
+ if outer:
+ if outer not in ('type', 'member', 'function'):
+ raise Exception('Internal error, unknown outer "%s".' % outer)
+ 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,
+ ) -> ASTDeclarator:
+ assert named in (True, False, 'single')
+ # now we should parse the name, and then suffixes
+ if named == 'single':
+ if self.match(identifier_re):
+ if self.matched_text in _keywords:
+ self.fail("Expected identifier, "
+ "got keyword: %s" % self.matched_text)
+ if self.matched_text in self.config.c_extra_keywords:
+ msg = "Expected identifier, got user-defined keyword: %s." \
+ + " Remove it from c_extra_keywords to allow it as identifier.\n" \
+ + "Currently c_extra_keywords is %s."
+ self.fail(msg % (self.matched_text,
+ str(self.config.c_extra_keywords)))
+ identifier = ASTIdentifier(self.matched_text)
+ declId = ASTNestedName([identifier], rooted=False)
+ 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()
+ static = False
+ const = False
+ volatile = False
+ restrict = False
+ while True:
+ if not static:
+ if self.skip_word_and_ws('static'):
+ static = True
+ continue
+ if not const:
+ if self.skip_word_and_ws('const'):
+ const = True
+ continue
+ if not volatile:
+ if self.skip_word_and_ws('volatile'):
+ volatile = True
+ continue
+ if not restrict:
+ if self.skip_word_and_ws('restrict'):
+ restrict = True
+ continue
+ break
+ vla = False if static else self.skip_string_and_ws('*')
+ if vla:
+ if not self.skip_string(']'):
+ self.fail("Expected ']' in end of array operator.")
+ size = None
+ else:
+ if self.skip_string(']'):
+ size = None
+ else:
+
+ def parser() -> ASTExpression:
+ return self._parse_expression()
+ size = self._parse_expression_fallback([']'], parser)
+ self.skip_ws()
+ if not self.skip_string(']'):
+ self.fail("Expected ']' in end of array operator.")
+ arrayOps.append(ASTArray(static, const, volatile, restrict, vla, size))
+ else:
+ break
+ param = self._parse_parameters(paramMode)
+ if param 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()
+ return ASTDeclaratorNameBitField(declId=declId, size=size)
+ return ASTDeclaratorNameParam(declId=declId, arrayOps=arrayOps,
+ param=param)
+
+ 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'):
+ raise Exception(
+ "Internal error, unknown paramMode '%s'." % paramMode)
+ prevErrors = []
+ self.skip_ws()
+ if typed and self.skip_string('*'):
+ self.skip_ws()
+ restrict = False
+ volatile = False
+ const = False
+ attrs = []
+ while 1:
+ if not restrict:
+ restrict = self.skip_word_and_ws('restrict')
+ if restrict:
+ continue
+ 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:
+ attrs.append(attr)
+ continue
+ break
+ next = self._parse_declarator(named, paramMode, typed)
+ return ASTDeclaratorPtr(next=next,
+ restrict=restrict, volatile=volatile, const=const,
+ attrs=ASTAttributeList(attrs))
+ if typed and self.current_char == '(': # note: peeking, not skipping
+ # maybe this is the beginning of params, try that first,
+ # otherwise assume it's noptr->declarator > ( ptr-declarator )
+ pos = self.pos
+ try:
+ # assume this is params
+ res = self._parse_declarator_name_suffix(named, paramMode,
+ typed)
+ return res
+ except DefinitionError as exParamQual:
+ msg = "If declarator-id with parameters"
+ if paramMode == 'function':
+ msg += " (e.g., 'void f(int arg)')"
+ prevErrors.append((exParamQual, msg))
+ 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 b
+ # 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
+ msg = "If parenthesis in noptr-declarator"
+ if paramMode == 'function':
+ msg += " (e.g., 'void (*f(int arg))(double)')"
+ prevErrors.append((exNoPtrParen, msg))
+ header = "Error in declarator"
+ raise self._make_multi_error(prevErrors, header) from exNoPtrParen
+ pos = self.pos
+ try:
+ return self._parse_declarator_name_suffix(named, paramMode, typed)
+ except DefinitionError as e:
+ self.pos = pos
+ prevErrors.append((e, "If declarator-id"))
+ header = "Error in declarator or parameters"
+ raise self._make_multi_error(prevErrors, header) from e
+
+ def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
+ ) -> ASTInitializer | None:
+ self.skip_ws()
+ if outer == 'member' and False: # NoQA: SIM223 # TODO
+ 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 is None: # function parameter
+ fallbackEnd = [',', ')']
+ else:
+ self.fail("Internal error, initializer for outer '%s' not "
+ "implemented." % outer)
+
+ def parser() -> ASTExpression:
+ return self._parse_assignment_expression()
+
+ 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|'single'|True: 'single' is e.g., for function objects which
+ doesn't need to name the arguments, but otherwise is a single name
+ """
+ if outer: # always named
+ if outer not in ('type', 'member', 'function'):
+ raise Exception('Internal error, unknown outer "%s".' % outer)
+ assert named
+
+ if outer == 'type':
+ # We allow type objects to just be a name.
+ 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)
+ self.assert_end(allowSemicolon=True)
+ except DefinitionError as exUntyped:
+ desc = "If just a name"
+ 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
+ desc = "If typedef-like declaration"
+ 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:
+ header = "Type must be either just a name or a "
+ header += "typedef-like declaration."
+ 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)
+ elif outer == 'function':
+ declSpecs = self._parse_decl_specs(outer=outer)
+ decl = self._parse_declarator(named=True, paramMode=outer)
+ else:
+ paramMode = 'type'
+ if outer == 'member': # i.e., member
+ named = True
+ 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 | None) -> ASTTypeWithInit:
+ if outer:
+ assert outer in ('type', 'member', 'function')
+ type = self._parse_type(outer=outer, named=named)
+ init = self._parse_initializer(outer=outer)
+ return ASTTypeWithInit(type, init)
+
+ def _parse_macro(self) -> ASTMacro:
+ self.skip_ws()
+ ident = self._parse_nested_name()
+ if ident is None:
+ self.fail("Expected identifier in macro definition.")
+ self.skip_ws()
+ if not self.skip_string_and_ws('('):
+ return ASTMacro(ident, None)
+ if self.skip_string(')'):
+ return ASTMacro(ident, [])
+ args = []
+ while 1:
+ self.skip_ws()
+ if self.skip_string('...'):
+ args.append(ASTMacroParameter(None, True))
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail('Expected ")" after "..." in macro parameters.')
+ break
+ if not self.match(identifier_re):
+ self.fail("Expected identifier in macro parameters.")
+ nn = ASTNestedName([ASTIdentifier(self.matched_text)], rooted=False)
+ # Allow named variadic args:
+ # https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
+ self.skip_ws()
+ if self.skip_string_and_ws('...'):
+ args.append(ASTMacroParameter(nn, False, True))
+ self.skip_ws()
+ if not self.skip_string(')'):
+ self.fail('Expected ")" after "..." in macro parameters.')
+ break
+ args.append(ASTMacroParameter(nn))
+ if self.skip_string_and_ws(','):
+ continue
+ if self.skip_string_and_ws(')'):
+ break
+ self.fail("Expected identifier, ')', or ',' in macro parameter list.")
+ return ASTMacro(ident, args)
+
+ def _parse_struct(self) -> ASTStruct:
+ name = self._parse_nested_name()
+ return ASTStruct(name)
+
+ def _parse_union(self) -> ASTUnion:
+ name = self._parse_nested_name()
+ return ASTUnion(name)
+
+ def _parse_enum(self) -> ASTEnum:
+ name = self._parse_nested_name()
+ return ASTEnum(name)
+
+ 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()
+
+ initVal = self._parse_expression_fallback([], parser)
+ init = ASTInitializer(initVal)
+ return ASTEnumerator(name, init, attrs)
+
+ def parse_declaration(self, objectType: str, directiveType: str) -> ASTDeclaration:
+ if objectType not in ('function', 'member',
+ 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
+ raise Exception('Internal error, unknown objectType "%s".' % objectType)
+ if directiveType not in ('function', 'member', 'var',
+ 'macro', 'struct', 'union', 'enum', 'enumerator', 'type'):
+ raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
+
+ declaration: DeclarationType | None = None
+ if objectType == 'member':
+ declaration = self._parse_type_with_init(named=True, outer='member')
+ elif objectType == 'function':
+ declaration = self._parse_type(named=True, outer='function')
+ elif objectType == 'macro':
+ declaration = self._parse_macro()
+ elif objectType == 'struct':
+ declaration = self._parse_struct()
+ elif objectType == 'union':
+ declaration = self._parse_union()
+ elif objectType == 'enum':
+ declaration = self._parse_enum()
+ elif objectType == 'enumerator':
+ declaration = self._parse_enumerator()
+ elif objectType == 'type':
+ declaration = self._parse_type(named=True, outer='type')
+ else:
+ raise AssertionError
+ if objectType != 'macro':
+ self.skip_ws()
+ semicolon = self.skip_string(';')
+ else:
+ semicolon = False
+ return ASTDeclaration(objectType, directiveType, declaration, semicolon)
+
+ def parse_namespace_object(self) -> ASTNestedName:
+ return self._parse_nested_name()
+
+ def parse_xref_object(self) -> ASTNestedName:
+ name = self._parse_nested_name()
+ # if there are '()' left, just skip them
+ self.skip_ws()
+ self.skip_string('()')
+ self.assert_end()
+ return name
+
+ def parse_expression(self) -> ASTExpression | ASTType:
+ pos = self.pos
+ res: ASTExpression | ASTType | None = None
+ try:
+ res = self._parse_expression()
+ self.skip_ws()
+ self.assert_end()
+ except DefinitionError as exExpr:
+ self.pos = pos
+ try:
+ res = self._parse_type(False)
+ self.skip_ws()
+ self.assert_end()
+ 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
+ return res
diff --git a/sphinx/domains/c/_symbol.py b/sphinx/domains/c/_symbol.py
new file mode 100644
index 0000000..5205204
--- /dev/null
+++ b/sphinx/domains/c/_symbol.py
@@ -0,0 +1,700 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Callable
+
+from sphinx.domains.c._ast import (
+ ASTDeclaration,
+ ASTIdentifier,
+ ASTNestedName,
+)
+from sphinx.locale import __
+from sphinx.util import logging
+
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+ from typing_extensions import Self
+
+ from sphinx.environment import BuildEnvironment
+
+logger = logging.getLogger(__name__)
+
+
+class _DuplicateSymbolError(Exception):
+ def __init__(self, symbol: Symbol, declaration: ASTDeclaration) -> None:
+ assert symbol
+ assert declaration
+ self.symbol = symbol
+ self.declaration = declaration
+
+ def __str__(self) -> str:
+ return "Internal C duplicate symbol error:\n%s" % self.symbol.dump(0)
+
+
+class SymbolLookupResult:
+ def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol,
+ ident: ASTIdentifier) -> None:
+ self.symbols = symbols
+ self.parentSymbol = parentSymbol
+ self.ident = ident
+
+
+class LookupKey:
+ def __init__(self, data: list[tuple[ASTIdentifier, str]]) -> None:
+ self.data = data
+
+ def __str__(self) -> str:
+ inner = ', '.join(f"({ident}, {id_})" for ident, id_ in self.data)
+ return f'[{inner}]'
+
+
+class Symbol:
+ debug_indent = 0
+ debug_indent_string = " "
+ debug_lookup = False
+ debug_show_tree = False
+
+ def __copy__(self) -> Self:
+ raise AssertionError # shouldn't happen
+
+ def __deepcopy__(self, memo: Any) -> Symbol:
+ if self.parent:
+ raise AssertionError # shouldn't happen
+ # the domain base class makes a copy of the initial data, which is fine
+ return Symbol(None, None, None, None, None)
+
+ @staticmethod
+ def debug_print(*args: Any) -> None:
+ msg = Symbol.debug_indent_string * Symbol.debug_indent
+ msg += "".join(str(e) for e in args)
+ logger.debug(msg)
+
+ def _assert_invariants(self) -> None:
+ if not self.parent:
+ # parent == None means global scope, so declaration means a parent
+ 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,
+ ident: ASTIdentifier | None,
+ 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.ident = ident
+ 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_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_function_params()
+
+ def _add_function_params(self) -> None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_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 function parameters, if any
+ if self.declaration is not None and self.declaration.function_params is not None:
+ for p in self.declaration.function_params:
+ if p.arg is None:
+ continue
+ nn = p.arg.name
+ if nn is None:
+ continue
+ # (comparing to the template params: we have checked that we are a declaration)
+ decl = ASTDeclaration('functionParam', None, p)
+ 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:
+ 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
+
+ def get_all_symbols(self) -> Iterator[Symbol]:
+ yield self
+ for sChild in self._children:
+ yield from sChild.get_all_symbols()
+
+ @property
+ def children(self) -> Iterator[Symbol]:
+ yield from self._children
+
+ @property
+ def children_recurse_anon(self) -> Iterator[Symbol]:
+ for c in self._children:
+ yield c
+ if not c.ident.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:
+ if s.declaration is not None:
+ # TODO: do we need the ID?
+ key.append((s.ident, s.declaration.get_newest_id()))
+ else:
+ key.append((s.ident, 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 = [s.ident for s in symbols]
+ return ASTNestedName(names, rooted=False)
+
+ def _find_first_named_symbol(self, ident: ASTIdentifier,
+ matchSelf: bool, recurseInAnon: bool) -> Symbol | None:
+ # TODO: further simplification from C++ to C
+ if Symbol.debug_lookup:
+ Symbol.debug_print("_find_first_named_symbol ->")
+ res = self._find_named_symbols(ident, matchSelf, recurseInAnon,
+ searchInSiblings=False)
+ try:
+ return next(res)
+ except StopIteration:
+ return None
+
+ def _find_named_symbols(self, ident: ASTIdentifier,
+ matchSelf: bool, recurseInAnon: bool,
+ searchInSiblings: bool) -> Iterator[Symbol]:
+ # TODO: further simplification from C++ to C
+ 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, addEndNewline=False))
+ Symbol.debug_print("ident: ", ident)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ def candidates() -> Iterator[Symbol]:
+ s = self
+ if Symbol.debug_lookup:
+ Symbol.debug_print("searching in self:")
+ logger.debug(s.to_string(Symbol.debug_indent + 1, addEndNewline=False))
+ 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, addEndNewline=False))
+
+ for s in candidates():
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candidate:")
+ logger.debug(s.to_string(Symbol.debug_indent + 1, addEndNewline=False))
+ if s.ident == ident:
+ 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,
+ onMissingQualifiedSymbol: Callable[[Symbol, ASTIdentifier], Symbol | None],
+ ancestorLookupType: str | None,
+ matchSelf: bool,
+ recurseInAnon: bool,
+ searchInSiblings: bool,
+ ) -> SymbolLookupResult | None:
+ # TODO: further simplification from C++ to C
+ # 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, addEndNewline=False))
+ Symbol.debug_print("nestedName: ", nestedName)
+ Symbol.debug_print("ancestorLookupType:", ancestorLookupType)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ 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]
+ while parentSymbol.parent:
+ if parentSymbol.find_identifier(firstName,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ searchInSiblings=searchInSiblings):
+ break
+ parentSymbol = parentSymbol.parent
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("starting point:")
+ logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1, addEndNewline=False))
+
+ # and now the actual lookup
+ for ident in names[:-1]:
+ symbol = parentSymbol._find_first_named_symbol(
+ ident, matchSelf=matchSelf, recurseInAnon=recurseInAnon)
+ if symbol is None:
+ symbol = onMissingQualifiedSymbol(parentSymbol, ident)
+ 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, addEndNewline=False))
+
+ # handle the last name
+ ident = names[-1]
+
+ symbols = parentSymbol._find_named_symbols(
+ ident, matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ searchInSiblings=searchInSiblings)
+ if Symbol.debug_lookup:
+ symbols = list(symbols) # type: ignore[assignment]
+ Symbol.debug_indent -= 2
+ return SymbolLookupResult(symbols, parentSymbol, ident)
+
+ def _add_symbols(
+ self,
+ nestedName: ASTNestedName,
+ declaration: ASTDeclaration | None,
+ docname: str | None,
+ line: int | None,
+ ) -> Symbol:
+ # TODO: further simplification from C++ to C
+ # Used for adding a whole path of symbols, where the last may or may not
+ # be an actual declaration.
+
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("nn: ", nestedName)
+ Symbol.debug_print("decl: ", declaration)
+ Symbol.debug_print(f"location: {docname}:{line}")
+
+ def onMissingQualifiedSymbol(parentSymbol: Symbol, ident: ASTIdentifier) -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("ident: ", ident)
+ Symbol.debug_indent -= 2
+ return Symbol(parent=parentSymbol, ident=ident,
+ declaration=None, docname=None, line=None)
+
+ lookupResult = self._symbol_lookup(nestedName,
+ onMissingQualifiedSymbol,
+ ancestorLookupType=None,
+ matchSelf=False,
+ recurseInAnon=False,
+ 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("ident: ", lookupResult.ident)
+ Symbol.debug_print("declaration: ", declaration)
+ Symbol.debug_print(f"location: {docname}:{line}")
+ Symbol.debug_indent -= 1
+ symbol = Symbol(parent=lookupResult.parentSymbol,
+ ident=lookupResult.ident,
+ 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,
+ ident=lookupResult.ident,
+ 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:
+ 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, candSybmol is not None?:", candSymbol is not None,
+ )
+ 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
+ for otherChild in other._children:
+ ourChild = self._find_first_named_symbol(
+ ident=otherChild.ident, matchSelf=False,
+ recurseInAnon=False)
+ if ourChild is None:
+ # TODO: hmm, should we prune by docnames?
+ self._children.append(otherChild)
+ otherChild.parent = self
+ otherChild._assert_invariants()
+ 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 '.. c:%s:: %s'.")
+ msg = msg % (ourChild.docname, ourChild.line,
+ ourChild.declaration.directiveType, name)
+ logger.warning(msg, location=(otherChild.docname, otherChild.line))
+ else:
+ # Both have declarations, and in the same docname.
+ # This can apparently happen, it should be safe to
+ # just ignore it, right?
+ pass
+ ourChild.merge_with(otherChild, docnames, env)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+
+ def add_name(self, nestedName: ASTNestedName) -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("add_name:")
+ res = self._add_symbols(nestedName, declaration=None, docname=None, 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
+ res = self._add_symbols(nestedName, declaration, docname, line)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ return res
+
+ def find_identifier(self, ident: ASTIdentifier,
+ matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool,
+ ) -> Symbol | None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_identifier:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("ident: ", ident)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings:", searchInSiblings)
+ logger.debug(self.to_string(Symbol.debug_indent + 1, addEndNewline=False))
+ 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, addEndNewline=False))
+ Symbol.debug_indent -= 2
+ if matchSelf and current.ident == ident:
+ return current
+ children = current.children_recurse_anon if recurseInAnon else current._children
+ for s in children:
+ if s.ident == ident:
+ return s
+ if not searchInSiblings:
+ break
+ current = current.siblingAbove
+ return None
+
+ def direct_lookup(self, key: LookupKey) -> Symbol | None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("direct_lookup:")
+ Symbol.debug_indent += 1
+ s = self
+ for name, id_ in key.data:
+ res = None
+ for cand in s._children:
+ if cand.ident == name:
+ res = cand
+ break
+ s = res
+ if Symbol.debug_lookup:
+ Symbol.debug_print("name: ", name)
+ Symbol.debug_print("id: ", id_)
+ if s is not None:
+ logger.debug(s.to_string(Symbol.debug_indent + 1, addEndNewline=False))
+ 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_declaration(self, nestedName: ASTNestedName, typ: str,
+ matchSelf: bool, recurseInAnon: bool) -> Symbol | None:
+ # templateShorthand: missing template parameter lists for templates is ok
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_declaration:")
+
+ def onMissingQualifiedSymbol(
+ parentSymbol: Symbol,
+ ident: ASTIdentifier,
+ ) -> Symbol | None:
+ return None
+
+ lookupResult = self._symbol_lookup(nestedName,
+ onMissingQualifiedSymbol,
+ ancestorLookupType=typ,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ 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
+ return symbols[0]
+
+ def to_string(self, indent: int, *, addEndNewline: bool = True) -> str:
+ res = [Symbol.debug_indent_string * indent]
+ if not self.parent:
+ res.append('::')
+ else:
+ if self.ident:
+ res.append(str(self.ident))
+ else:
+ res.append(str(self.declaration))
+ if self.declaration:
+ res.append(": ")
+ if self.isRedeclaration:
+ res.append('!!duplicate!! ')
+ res.append(str(self.declaration))
+ if self.docname:
+ res.append('\t(')
+ res.append(self.docname)
+ res.append(')')
+ if addEndNewline:
+ res.append('\n')
+ return ''.join(res)
+
+ def dump(self, indent: int) -> str:
+ return ''.join([self.to_string(indent), *(c.dump(indent + 1) for c in self._children)])
diff --git a/sphinx/domains/changeset.py b/sphinx/domains/changeset.py
index 7cfe382..5ffabcf 100644
--- a/sphinx/domains/changeset.py
+++ b/sphinx/domains/changeset.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any, NamedTuple, cast
+from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, cast
from docutils import nodes
@@ -16,19 +16,21 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
versionlabels = {
- 'versionadded': _('New in version %s'),
+ 'versionadded': _('Added in version %s'),
'versionchanged': _('Changed in version %s'),
'deprecated': _('Deprecated since version %s'),
+ 'versionremoved': _('Removed in version %s'),
}
versionlabel_classes = {
'versionadded': 'added',
'versionchanged': 'changed',
'deprecated': 'deprecated',
+ 'versionremoved': 'removed',
}
@@ -45,11 +47,12 @@ class VersionChange(SphinxDirective):
"""
Directive to describe a change/addition/deprecation in a specific version.
"""
+
has_content = True
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
- option_spec: OptionSpec = {}
+ option_spec: ClassVar[OptionSpec] = {}
def run(self) -> list[Node]:
node = addnodes.versionmodified()
@@ -120,13 +123,13 @@ class ChangeSetDomain(Domain):
version = node['version']
module = self.env.ref_context.get('py:module')
objname = self.env.temp_data.get('object')
- changeset = ChangeSet(node['type'], self.env.docname, node.line,
+ changeset = ChangeSet(node['type'], self.env.docname, node.line, # type: ignore[arg-type]
module, objname, node.astext())
self.changesets.setdefault(version, []).append(changeset)
def clear_doc(self, docname: str) -> None:
for changes in self.changesets.values():
- for changeset in changes[:]:
+ for changeset in changes.copy():
if changeset.docname == docname:
changes.remove(changeset)
@@ -147,11 +150,12 @@ class ChangeSetDomain(Domain):
return self.changesets.get(version, [])
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(ChangeSetDomain)
app.add_directive('deprecated', VersionChange)
app.add_directive('versionadded', VersionChange)
app.add_directive('versionchanged', VersionChange)
+ app.add_directive('versionremoved', VersionChange)
return {
'version': 'builtin',
diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py
index d12c0f1..4f00feb 100644
--- a/sphinx/domains/citation.py
+++ b/sphinx/domains/citation.py
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import ExtensionMetadata
logger = logging.getLogger(__name__)
@@ -69,7 +70,7 @@ class CitationDomain(Domain):
path = self.env.doc2path(self.citations[label][0])
logger.warning(__('duplicate citation %s, other instance in %s'), label, path,
location=node, type='ref', subtype='citation')
- self.citations[label] = (node['docname'], node['ids'][0], node.line)
+ self.citations[label] = (node['docname'], node['ids'][0], node.line) # type: ignore[assignment]
def note_citation_reference(self, node: pending_xref) -> None:
docnames = self.citation_refs.setdefault(node['reftarget'], set())
@@ -103,6 +104,7 @@ class CitationDomain(Domain):
class CitationDefinitionTransform(SphinxTransform):
"""Mark citation definition labels as not smartquoted."""
+
default_priority = 619
def apply(self, **kwargs: Any) -> None:
@@ -122,6 +124,7 @@ class CitationReferenceTransform(SphinxTransform):
Replace citation references by pending_xref nodes before the default
docutils transform tries to resolve them.
"""
+
default_priority = 619
def apply(self, **kwargs: Any) -> None:
@@ -141,7 +144,7 @@ class CitationReferenceTransform(SphinxTransform):
domain.note_citation_reference(ref)
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(CitationDomain)
app.add_transform(CitationDefinitionTransform)
app.add_transform(CitationReferenceTransform)
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
deleted file mode 100644
index 80920c6..0000000
--- a/sphinx/domains/cpp.py
+++ /dev/null
@@ -1,8233 +0,0 @@
-"""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,
- }
diff --git a/sphinx/domains/cpp/__init__.py b/sphinx/domains/cpp/__init__.py
new file mode 100644
index 0000000..9e1f075
--- /dev/null
+++ b/sphinx/domains/cpp/__init__.py
@@ -0,0 +1,1181 @@
+"""The C++ language domain."""
+
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING, Any, ClassVar
+
+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.domains.cpp._ast import (
+ ASTDeclaration,
+ ASTIdentifier,
+ ASTNamespace,
+ ASTNestedName,
+ ASTNestedNameElement,
+)
+from sphinx.domains.cpp._ids import _max_id
+from sphinx.domains.cpp._parser import DefinitionParser
+from sphinx.domains.cpp._symbol import Symbol, _DuplicateSymbolError
+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 (
+ DefinitionError,
+ NoOldIdError,
+ anon_identifier_re,
+)
+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 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.domains.cpp._symbol import LookupKey
+ from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
+
+# re-export objects for backwards compatibility
+# xref https://github.com/sphinx-doc/sphinx/issues/12295
+from sphinx.domains.cpp._ast import ( # NoQA: F401
+ ASTAlignofExpr,
+ ASTArray,
+ ASTAssignmentExpr,
+ ASTBase,
+ ASTBaseClass,
+ ASTBinOpExpr,
+ ASTBooleanLiteral,
+ ASTBracedInitList,
+ ASTCastExpr,
+ ASTCharLiteral,
+ ASTClass,
+ ASTCommaExpr,
+ ASTConcept,
+ ASTConditionalExpr,
+ ASTDeclarator,
+ ASTDeclaratorMemPtr,
+ ASTDeclaratorNameBitField,
+ ASTDeclaratorNameParamQual,
+ ASTDeclaratorParamPack,
+ ASTDeclaratorParen,
+ ASTDeclaratorPtr,
+ ASTDeclaratorRef,
+ ASTDeclSpecs,
+ ASTDeclSpecsSimple,
+ ASTDeleteExpr,
+ ASTEnum,
+ ASTEnumerator,
+ ASTExplicitCast,
+ ASTExplicitSpec,
+ ASTExpression,
+ ASTFallbackExpr,
+ ASTFoldExpr,
+ ASTFunctionParameter,
+ ASTIdExpression,
+ ASTInitializer,
+ ASTLiteral,
+ ASTNewExpr,
+ ASTNoexceptExpr,
+ ASTNoexceptSpec,
+ ASTNumberLiteral,
+ ASTOperator,
+ ASTOperatorBuildIn,
+ ASTOperatorLiteral,
+ ASTOperatorType,
+ ASTPackExpansionExpr,
+ ASTParametersQualifiers,
+ ASTParenExpr,
+ ASTParenExprList,
+ ASTPointerLiteral,
+ ASTPostfixArray,
+ ASTPostfixCallExpr,
+ ASTPostfixDec,
+ ASTPostfixExpr,
+ ASTPostfixInc,
+ ASTPostfixMember,
+ ASTPostfixMemberOfPointer,
+ ASTPostfixOp,
+ ASTRequiresClause,
+ ASTSizeofExpr,
+ ASTSizeofParamPack,
+ ASTSizeofType,
+ ASTStringLiteral,
+ ASTTemplateArgConstant,
+ ASTTemplateArgs,
+ ASTTemplateDeclarationPrefix,
+ ASTTemplateIntroduction,
+ ASTTemplateIntroductionParameter,
+ ASTTemplateKeyParamPackIdDefault,
+ ASTTemplateParam,
+ ASTTemplateParamConstrainedTypeWithInit,
+ ASTTemplateParamNonType,
+ ASTTemplateParams,
+ ASTTemplateParamTemplateType,
+ ASTTemplateParamType,
+ ASTThisLiteral,
+ ASTTrailingTypeSpec,
+ ASTTrailingTypeSpecDecltype,
+ ASTTrailingTypeSpecDecltypeAuto,
+ ASTTrailingTypeSpecFundamental,
+ ASTTrailingTypeSpecName,
+ ASTType,
+ ASTTypeId,
+ ASTTypeUsing,
+ ASTTypeWithInit,
+ ASTUnaryOpExpr,
+ ASTUnion,
+ ASTUserDefinedLiteral,
+)
+
+logger = logging.getLogger(__name__)
+
+
+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: ClassVar[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.reverse()
+ 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: ClassVar[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: ClassVar[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: ClassVar[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: ClassVar[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[str, Any]) -> 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 | None:
+ 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 f'{parentName}::{target}'
+
+
+def setup(app: Sphinx) -> ExtensionMetadata:
+ 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: Sphinx) -> None:
+ 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,
+ }
diff --git a/sphinx/domains/cpp/_ast.py b/sphinx/domains/cpp/_ast.py
new file mode 100644
index 0000000..ad57695
--- /dev/null
+++ b/sphinx/domains/cpp/_ast.py
@@ -0,0 +1,3635 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.domains.cpp._ids import (
+ _id_char_from_prefix,
+ _id_explicit_cast,
+ _id_fundamental_v1,
+ _id_fundamental_v2,
+ _id_operator_unary_v2,
+ _id_operator_v1,
+ _id_operator_v2,
+ _id_prefix,
+ _id_shorthands_v1,
+ _max_id,
+)
+from sphinx.util.cfamily import (
+ ASTAttributeList,
+ ASTBaseBase,
+ ASTBaseParenExprList,
+ NoOldIdError,
+ StringifyTransform,
+ UnsupportedMultiCharacterCharLiteral,
+ verify_description_mode,
+)
+
+if TYPE_CHECKING:
+
+ from docutils.nodes import Element, TextElement
+
+ from sphinx.addnodes import desc_signature
+ from sphinx.domains.cpp._symbol import Symbol
+ from sphinx.environment import BuildEnvironment
+
+
+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
+
+ # ASTBaseBase already implements this method,
+ # but specialising it here improves performance
+ def __eq__(self, other: object) -> bool:
+ if type(other) is not ASTIdentifier:
+ return NotImplemented
+ return self.identifier == other.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) -> 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)
+ res.extend(n.get_id(version) for n in self.names)
+ 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) -> None:
+ 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 | None,
+ op: str, rightExpr: ASTExpression | None) -> 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) -> None:
+ 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) -> None:
+ # 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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:
+ return ''.join([
+ 'cl',
+ idPrefix,
+ *(e.get_id(version) for e in self.lst.exprs),
+ 'E',
+ ])
+
+ 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]) -> None:
+ self.prefix = prefix
+ self.postFixes = postFixes
+
+ def _stringify(self, transform: StringifyTransform) -> str:
+ return ''.join([transform(self.prefix), *(transform(p) for p in self.postFixes)])
+
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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]) -> None:
+ 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) -> None:
+ 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) -> None:
+ 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]) -> None:
+ 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) -> None:
+ 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 __eq__(self, other: object) -> bool:
+ raise NotImplementedError(repr(self))
+
+ 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 __eq__(self, other: object) -> bool:
+ if not isinstance(other, ASTOperatorBuildIn):
+ return NotImplemented
+ return self.op == other.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 __eq__(self, other: object) -> bool:
+ if not isinstance(other, ASTOperatorLiteral):
+ return NotImplemented
+ return self.identifier == other.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 __eq__(self, other: object) -> bool:
+ if not isinstance(other, ASTOperatorType):
+ return NotImplemented
+ return self.type == other.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 f'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) -> None:
+ 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) -> 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) -> None:
+ 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))
+ res.extend(transform(op) for op in self.arrayOps)
+ 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) -> None:
+ 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) -> 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 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) -> bool:
+ 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) -> None:
+ 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) -> 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")
+ res.extend(param.get_id(version) for param in self.params)
+ res.append("E")
+ if not excludeRequires and self.requiresClause:
+ res.extend(['IQ', self.requiresClause.expr.get_id(version), '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
+ return ''.join([
+ # first do the same as a normal template parameter list
+ "I",
+ *(param.get_id(version) for param in self.params),
+ "E",
+ # let's use X expr E, which is otherwise for constant template args
+ "X",
+ self.concept.get_id(version),
+ "I",
+ *(param.get_id_as_arg(version) for param in self.params),
+ "E",
+ "E",
+ ])
+
+ 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) -> 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:
+ return ''.join(map(transform, self.templates))
+
+ 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 = None
+ # set by CPPObject._add_enumerator_to_parent
+ self.enumeratorScopedSymbol: Symbol | None = None
+
+ # the cache assumes that by the time get_newest_id is called, no
+ # further changes will be made to this object
+ self._newest_id_cache: str | None = 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:
+ if self._newest_id_cache is None:
+ self._newest_id_cache = self.get_id(_max_id, True)
+ return self._newest_id_cache
+
+ 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[str, bool]) -> 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)
diff --git a/sphinx/domains/cpp/_ids.py b/sphinx/domains/cpp/_ids.py
new file mode 100644
index 0000000..ee8eb49
--- /dev/null
+++ b/sphinx/domains/cpp/_ids.py
@@ -0,0 +1,537 @@
+"""
+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 initializer
+ 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
+"""
+
+from __future__ import annotations
+
+import re
+
+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.DOTALL)
+_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',
+}
diff --git a/sphinx/domains/cpp/_parser.py b/sphinx/domains/cpp/_parser.py
new file mode 100644
index 0000000..ee6e8ba
--- /dev/null
+++ b/sphinx/domains/cpp/_parser.py
@@ -0,0 +1,2117 @@
+from __future__ import annotations
+
+import re
+from typing import TYPE_CHECKING, Any, Callable
+
+from sphinx.domains.cpp._ast import (
+ ASTAlignofExpr,
+ ASTArray,
+ ASTAssignmentExpr,
+ ASTBaseClass,
+ ASTBinOpExpr,
+ ASTBooleanLiteral,
+ ASTBracedInitList,
+ ASTCastExpr,
+ ASTCharLiteral,
+ ASTClass,
+ ASTCommaExpr,
+ ASTConcept,
+ ASTConditionalExpr,
+ ASTDeclaration,
+ ASTDeclarator,
+ ASTDeclaratorMemPtr,
+ ASTDeclaratorNameBitField,
+ ASTDeclaratorNameParamQual,
+ ASTDeclaratorParamPack,
+ ASTDeclaratorParen,
+ ASTDeclaratorPtr,
+ ASTDeclaratorRef,
+ ASTDeclSpecs,
+ ASTDeclSpecsSimple,
+ ASTDeleteExpr,
+ ASTEnum,
+ ASTEnumerator,
+ ASTExplicitCast,
+ ASTExplicitSpec,
+ ASTExpression,
+ ASTFallbackExpr,
+ ASTFoldExpr,
+ ASTFunctionParameter,
+ ASTIdentifier,
+ ASTIdExpression,
+ ASTInitializer,
+ ASTLiteral,
+ ASTNamespace,
+ ASTNestedName,
+ ASTNestedNameElement,
+ ASTNewExpr,
+ ASTNoexceptExpr,
+ ASTNoexceptSpec,
+ ASTNumberLiteral,
+ ASTOperator,
+ ASTOperatorBuildIn,
+ ASTOperatorLiteral,
+ ASTOperatorType,
+ ASTPackExpansionExpr,
+ ASTParametersQualifiers,
+ ASTParenExpr,
+ ASTParenExprList,
+ ASTPointerLiteral,
+ ASTPostfixArray,
+ ASTPostfixCallExpr,
+ ASTPostfixDec,
+ ASTPostfixExpr,
+ ASTPostfixInc,
+ ASTPostfixMember,
+ ASTPostfixMemberOfPointer,
+ ASTPostfixOp,
+ ASTRequiresClause,
+ ASTSizeofExpr,
+ ASTSizeofParamPack,
+ ASTSizeofType,
+ ASTStringLiteral,
+ ASTTemplateArgConstant,
+ ASTTemplateArgs,
+ ASTTemplateDeclarationPrefix,
+ ASTTemplateIntroduction,
+ ASTTemplateIntroductionParameter,
+ ASTTemplateKeyParamPackIdDefault,
+ ASTTemplateParam,
+ ASTTemplateParamConstrainedTypeWithInit,
+ ASTTemplateParamNonType,
+ ASTTemplateParams,
+ ASTTemplateParamTemplateType,
+ ASTTemplateParamType,
+ ASTThisLiteral,
+ ASTTrailingTypeSpec,
+ ASTTrailingTypeSpecDecltype,
+ ASTTrailingTypeSpecDecltypeAuto,
+ ASTTrailingTypeSpecFundamental,
+ ASTTrailingTypeSpecName,
+ ASTType,
+ ASTTypeId,
+ ASTTypeUsing,
+ ASTTypeWithInit,
+ ASTUnaryOpExpr,
+ ASTUnion,
+ ASTUserDefinedLiteral,
+)
+from sphinx.domains.cpp._ids import (
+ _expression_assignment_ops,
+ _expression_bin_ops,
+ _expression_unary_ops,
+ _fold_operator_re,
+ _id_explicit_cast,
+ _keywords,
+ _operator_re,
+ _simple_type_specifiers_re,
+ _string_re,
+ _visibility_re,
+ udl_identifier_re,
+)
+from sphinx.util import logging
+from sphinx.util.cfamily import (
+ ASTAttributeList,
+ BaseParser,
+ DefinitionError,
+ UnsupportedMultiCharacterCharLiteral,
+ 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,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Sequence
+
+logger = logging.getLogger(__name__)
+
+
+class DefinitionParser(BaseParser):
+ @property
+ def language(self) -> str:
+ return 'C++'
+
+ @property
+ def id_attributes(self) -> Sequence[str]:
+ return self.config.cpp_id_attributes
+
+ @property
+ def paren_attributes(self) -> Sequence[str]:
+ 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 | None:
+ # "(" 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 == '}':
+ 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 == 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 = 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 | None:
+ 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 | None:
+ # 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 | None:
+ 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 = 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 compared 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] = [
+ ASTTemplateParams([], requiresClause=None)
+ for _i in range(numExtra)
+ ]
+ 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
diff --git a/sphinx/domains/cpp/_symbol.py b/sphinx/domains/cpp/_symbol.py
new file mode 100644
index 0000000..4caa430
--- /dev/null
+++ b/sphinx/domains/cpp/_symbol.py
@@ -0,0 +1,1092 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Callable, NoReturn
+
+from sphinx.domains.cpp._ast import (
+ ASTDeclaration,
+ ASTIdentifier,
+ ASTNestedName,
+ ASTNestedNameElement,
+ ASTOperator,
+ ASTTemplateArgs,
+ ASTTemplateDeclarationPrefix,
+ ASTTemplateIntroduction,
+ ASTTemplateParams,
+)
+from sphinx.locale import __
+from sphinx.util import logging
+
+if TYPE_CHECKING:
+ from collections.abc import Iterator
+
+ from sphinx.environment import BuildEnvironment
+
+logger = logging.getLogger(__name__)
+
+
+class _DuplicateSymbolError(Exception):
+ def __init__(self, symbol: Symbol, declaration: ASTDeclaration) -> None:
+ assert symbol
+ assert declaration
+ self.symbol = symbol
+ self.declaration = declaration
+
+ def __str__(self) -> str:
+ return "Internal C++ duplicate symbol error:\n%s" % self.symbol.dump(0)
+
+
+class SymbolLookupResult:
+ def __init__(self, symbols: Iterator[Symbol], parentSymbol: Symbol,
+ identOrOp: ASTIdentifier | ASTOperator, templateParams: Any,
+ templateArgs: ASTTemplateArgs) -> None:
+ self.symbols = symbols
+ self.parentSymbol = parentSymbol
+ self.identOrOp = identOrOp
+ self.templateParams = templateParams
+ self.templateArgs = templateArgs
+
+
+class LookupKey:
+ def __init__(self, data: list[tuple[ASTNestedNameElement,
+ ASTTemplateParams | ASTTemplateIntroduction,
+ str]]) -> None:
+ self.data = data
+
+
+def _is_specialization(templateParams: ASTTemplateParams | ASTTemplateIntroduction,
+ templateArgs: ASTTemplateArgs) -> bool:
+ # Checks if `templateArgs` does not exactly match `templateParams`.
+ # the names of the template parameters must be given exactly as args
+ # and params that are packs must in the args be the name expanded
+ if len(templateParams.params) != len(templateArgs.args):
+ return True
+ # having no template params and no arguments is also a specialization
+ if len(templateParams.params) == 0:
+ return True
+ for i in range(len(templateParams.params)):
+ param = templateParams.params[i]
+ arg = templateArgs.args[i]
+ # TODO: doing this by string manipulation is probably not the most efficient
+ paramName = str(param.name)
+ argTxt = str(arg)
+ isArgPackExpansion = argTxt.endswith('...')
+ if param.isPack != isArgPackExpansion:
+ return True
+ argName = argTxt[:-3] if isArgPackExpansion else argTxt
+ if paramName != argName:
+ return True
+ return False
+
+
+class Symbol:
+ debug_indent = 0
+ debug_indent_string = " "
+ debug_lookup = False # overridden by the corresponding config value
+ debug_show_tree = False # overridden by the corresponding config value
+
+ def __copy__(self) -> NoReturn:
+ raise AssertionError # shouldn't happen
+
+ def __deepcopy__(self, memo: Any) -> Symbol:
+ if self.parent:
+ raise AssertionError # shouldn't happen
+ # the domain base class makes a copy of the initial data, which is fine
+ return Symbol(None, None, None, None, None, None, None)
+
+ @staticmethod
+ def debug_print(*args: Any) -> None:
+ logger.debug(Symbol.debug_indent_string * Symbol.debug_indent, end="")
+ logger.debug(*args)
+
+ def _assert_invariants(self) -> None:
+ if not self.parent:
+ # parent == None means global scope, so declaration means a parent
+ assert not self.identOrOp
+ assert not self.templateParams
+ assert not self.templateArgs
+ assert not self.declaration
+ assert not self.docname
+ else:
+ if self.declaration:
+ assert self.docname
+
+ def __setattr__(self, key: str, value: Any) -> None:
+ if key == "children":
+ raise AssertionError
+ return super().__setattr__(key, value)
+
+ def __init__(self, parent: Symbol | None,
+ identOrOp: ASTIdentifier | ASTOperator | None,
+ templateParams: ASTTemplateParams | ASTTemplateIntroduction | None,
+ templateArgs: Any, declaration: ASTDeclaration | None,
+ docname: str | None, line: int | None) -> None:
+ self.parent = parent
+ # declarations in a single directive are linked together
+ self.siblingAbove: Symbol | None = None
+ self.siblingBelow: Symbol | None = None
+ self.identOrOp = identOrOp
+ # Ensure the same symbol for `A` is created for:
+ #
+ # .. cpp:class:: template <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) -> Iterator[Symbol]:
+ for c in self._children:
+ yield c
+ if not c.identOrOp.is_anon():
+ continue
+
+ yield from c.children_recurse_anon
+
+ def get_lookup_key(self) -> LookupKey:
+ # The pickle files for the environment and for each document are distinct.
+ # The environment has all the symbols, but the documents has xrefs that
+ # must know their scope. A lookup key is essentially a specification of
+ # how to find a specific symbol.
+ symbols = []
+ s = self
+ while s.parent:
+ symbols.append(s)
+ s = s.parent
+ symbols.reverse()
+ key = []
+ for s in symbols:
+ nne = ASTNestedNameElement(s.identOrOp, s.templateArgs)
+ if s.declaration is not None:
+ key.append((nne, s.templateParams, s.declaration.get_newest_id()))
+ else:
+ key.append((nne, s.templateParams, None))
+ return LookupKey(key)
+
+ def get_full_nested_name(self) -> ASTNestedName:
+ symbols = []
+ s = self
+ while s.parent:
+ symbols.append(s)
+ s = s.parent
+ symbols.reverse()
+ names = []
+ templates = []
+ for s in symbols:
+ names.append(ASTNestedNameElement(s.identOrOp, s.templateArgs))
+ templates.append(False)
+ return ASTNestedName(names, templates, rooted=False)
+
+ def _find_first_named_symbol(self, identOrOp: ASTIdentifier | ASTOperator,
+ templateParams: ASTTemplateParams | ASTTemplateIntroduction,
+ templateArgs: ASTTemplateArgs | None,
+ templateShorthand: bool, matchSelf: bool,
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ ) -> Symbol | None:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("_find_first_named_symbol ->")
+ res = self._find_named_symbols(identOrOp, templateParams, templateArgs,
+ templateShorthand, matchSelf, recurseInAnon,
+ correctPrimaryTemplateArgs,
+ searchInSiblings=False)
+ try:
+ return next(res)
+ except StopIteration:
+ return None
+
+ def _find_named_symbols(self, identOrOp: ASTIdentifier | ASTOperator,
+ templateParams: ASTTemplateParams | ASTTemplateIntroduction,
+ templateArgs: ASTTemplateArgs,
+ templateShorthand: bool, matchSelf: bool,
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ searchInSiblings: bool) -> Iterator[Symbol]:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_find_named_symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ logger.debug(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("templateParams: ", templateParams)
+ Symbol.debug_print("templateArgs: ", templateArgs)
+ Symbol.debug_print("templateShorthand: ", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("correctPrimaryTemplateAargs:", correctPrimaryTemplateArgs)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ if correctPrimaryTemplateArgs:
+ if templateParams is not None and templateArgs is not None:
+ # If both are given, but it's not a specialization, then do lookup as if
+ # there is no argument list.
+ # For example: template<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() -> Iterator[Symbol]:
+ s = self
+ if Symbol.debug_lookup:
+ Symbol.debug_print("searching in self:")
+ logger.debug(s.to_string(Symbol.debug_indent + 1), end="")
+ while True:
+ if matchSelf:
+ yield s
+ if recurseInAnon:
+ yield from s.children_recurse_anon
+ else:
+ yield from s._children
+
+ if s.siblingAbove is None:
+ break
+ s = s.siblingAbove
+ if Symbol.debug_lookup:
+ Symbol.debug_print("searching in sibling:")
+ logger.debug(s.to_string(Symbol.debug_indent + 1), end="")
+
+ for s in candidates():
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candidate:")
+ logger.debug(s.to_string(Symbol.debug_indent + 1), end="")
+ if matches(s):
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("matches")
+ Symbol.debug_indent -= 3
+ yield s
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 2
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+
+ def _symbol_lookup(
+ self,
+ nestedName: ASTNestedName,
+ templateDecls: list[Any],
+ onMissingQualifiedSymbol: Callable[
+ [Symbol, ASTIdentifier | ASTOperator, Any, ASTTemplateArgs], Symbol | None,
+ ],
+ strictTemplateParamArgLists: bool, ancestorLookupType: str,
+ templateShorthand: bool, matchSelf: bool,
+ recurseInAnon: bool, correctPrimaryTemplateArgs: bool,
+ searchInSiblings: bool,
+ ) -> SymbolLookupResult:
+ # ancestorLookupType: if not None, specifies the target type of the lookup
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_symbol_lookup:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ logger.debug(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("nestedName: ", nestedName)
+ Symbol.debug_print("templateDecls: ", ",".join(str(t) for t in templateDecls))
+ Symbol.debug_print("strictTemplateParamArgLists:", strictTemplateParamArgLists)
+ Symbol.debug_print("ancestorLookupType:", ancestorLookupType)
+ Symbol.debug_print("templateShorthand: ", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("correctPrimaryTemplateArgs: ", correctPrimaryTemplateArgs)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ if strictTemplateParamArgLists:
+ # Each template argument list must have a template parameter list.
+ # But to declare a template there must be an additional template parameter list.
+ assert (nestedName.num_templates() == len(templateDecls) or
+ nestedName.num_templates() + 1 == len(templateDecls))
+ else:
+ assert len(templateDecls) <= nestedName.num_templates() + 1
+
+ names = nestedName.names
+
+ # find the right starting point for lookup
+ parentSymbol = self
+ if nestedName.rooted:
+ while parentSymbol.parent:
+ parentSymbol = parentSymbol.parent
+ if ancestorLookupType is not None:
+ # walk up until we find the first identifier
+ firstName = names[0]
+ if not firstName.is_operator():
+ while parentSymbol.parent:
+ if parentSymbol.find_identifier(firstName.identOrOp,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ searchInSiblings=searchInSiblings):
+ # if we are in the scope of a constructor but wants to
+ # reference the class we need to walk one extra up
+ if (len(names) == 1 and ancestorLookupType == 'class' and matchSelf and
+ parentSymbol.parent and
+ parentSymbol.parent.identOrOp == firstName.identOrOp):
+ pass
+ else:
+ break
+ parentSymbol = parentSymbol.parent
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("starting point:")
+ logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="")
+
+ # and now the actual lookup
+ iTemplateDecl = 0
+ for name in names[:-1]:
+ identOrOp = name.identOrOp
+ templateArgs = name.templateArgs
+ if strictTemplateParamArgLists:
+ # there must be a parameter list
+ if templateArgs:
+ assert iTemplateDecl < len(templateDecls)
+ templateParams = templateDecls[iTemplateDecl]
+ iTemplateDecl += 1
+ else:
+ templateParams = None
+ else:
+ # take the next template parameter list if there is one
+ # otherwise it's ok
+ if templateArgs and iTemplateDecl < len(templateDecls):
+ templateParams = templateDecls[iTemplateDecl]
+ iTemplateDecl += 1
+ else:
+ templateParams = None
+
+ symbol = parentSymbol._find_first_named_symbol(
+ identOrOp,
+ templateParams, templateArgs,
+ templateShorthand=templateShorthand,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ correctPrimaryTemplateArgs=correctPrimaryTemplateArgs)
+ if symbol is None:
+ symbol = onMissingQualifiedSymbol(parentSymbol, identOrOp,
+ templateParams, templateArgs)
+ if symbol is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return None
+ # We have now matched part of a nested name, and need to match more
+ # so even if we should matchSelf before, we definitely shouldn't
+ # even more. (see also issue #2666)
+ matchSelf = False
+ parentSymbol = symbol
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("handle last name from:")
+ logger.debug(parentSymbol.to_string(Symbol.debug_indent + 1), end="")
+
+ # handle the last name
+ name = names[-1]
+ identOrOp = name.identOrOp
+ templateArgs = name.templateArgs
+ if iTemplateDecl < len(templateDecls):
+ assert iTemplateDecl + 1 == len(templateDecls)
+ templateParams = templateDecls[iTemplateDecl]
+ else:
+ assert iTemplateDecl == len(templateDecls)
+ templateParams = None
+
+ symbols = parentSymbol._find_named_symbols(
+ identOrOp, templateParams, templateArgs,
+ templateShorthand=templateShorthand, matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False,
+ searchInSiblings=searchInSiblings)
+ if Symbol.debug_lookup:
+ symbols = list(symbols) # type: ignore[assignment]
+ Symbol.debug_indent -= 2
+ return SymbolLookupResult(symbols, parentSymbol,
+ identOrOp, templateParams, templateArgs)
+
+ def _add_symbols(
+ self,
+ nestedName: ASTNestedName,
+ templateDecls: list[Any],
+ declaration: ASTDeclaration | None,
+ docname: str | None,
+ line: int | None,
+ ) -> Symbol:
+ # Used for adding a whole path of symbols, where the last may or may not
+ # be an actual declaration.
+
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("tdecls:", ",".join(str(t) for t in templateDecls))
+ Symbol.debug_print("nn: ", nestedName)
+ Symbol.debug_print("decl: ", declaration)
+ Symbol.debug_print(f"location: {docname}:{line}")
+
+ def onMissingQualifiedSymbol(parentSymbol: Symbol,
+ identOrOp: ASTIdentifier | ASTOperator,
+ templateParams: Any, templateArgs: ASTTemplateArgs,
+ ) -> Symbol | None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("_add_symbols, onMissingQualifiedSymbol:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("templateParams:", templateParams)
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("templateARgs: ", templateArgs)
+ Symbol.debug_indent -= 2
+ return Symbol(parent=parentSymbol, identOrOp=identOrOp,
+ templateParams=templateParams,
+ templateArgs=templateArgs, declaration=None,
+ docname=None, line=None)
+
+ lookupResult = self._symbol_lookup(nestedName, templateDecls,
+ onMissingQualifiedSymbol,
+ strictTemplateParamArgLists=True,
+ ancestorLookupType=None,
+ templateShorthand=False,
+ matchSelf=False,
+ recurseInAnon=False,
+ correctPrimaryTemplateArgs=True,
+ searchInSiblings=False)
+ assert lookupResult is not None # we create symbols all the way, so that can't happen
+ symbols = list(lookupResult.symbols)
+ if len(symbols) == 0:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("_add_symbols, result, no symbol:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("templateParams:", lookupResult.templateParams)
+ Symbol.debug_print("identOrOp: ", lookupResult.identOrOp)
+ Symbol.debug_print("templateArgs: ", lookupResult.templateArgs)
+ Symbol.debug_print("declaration: ", declaration)
+ Symbol.debug_print(f"location: {docname}:{line}")
+ Symbol.debug_indent -= 1
+ symbol = Symbol(parent=lookupResult.parentSymbol,
+ identOrOp=lookupResult.identOrOp,
+ templateParams=lookupResult.templateParams,
+ templateArgs=lookupResult.templateArgs,
+ declaration=declaration,
+ docname=docname, line=line)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return symbol
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("_add_symbols, result, symbols:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("number symbols:", len(symbols))
+ Symbol.debug_indent -= 1
+
+ if not declaration:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("no declaration")
+ Symbol.debug_indent -= 2
+ # good, just a scope creation
+ # TODO: what if we have more than one symbol?
+ return symbols[0]
+
+ noDecl = []
+ withDecl = []
+ dupDecl = []
+ for s in symbols:
+ if s.declaration is None:
+ noDecl.append(s)
+ elif s.isRedeclaration:
+ dupDecl.append(s)
+ else:
+ withDecl.append(s)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("#noDecl: ", len(noDecl))
+ Symbol.debug_print("#withDecl:", len(withDecl))
+ Symbol.debug_print("#dupDecl: ", len(dupDecl))
+ # With partial builds we may start with a large symbol tree stripped of declarations.
+ # Essentially any combination of noDecl, withDecl, and dupDecls seems possible.
+ # TODO: make partial builds fully work. What should happen when the primary symbol gets
+ # deleted, and other duplicates exist? The full document should probably be rebuild.
+
+ # First check if one of those with a declaration matches.
+ # If it's a function, we need to compare IDs,
+ # otherwise there should be only one symbol with a declaration.
+ def makeCandSymbol() -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("begin: creating candidate symbol")
+ symbol = Symbol(parent=lookupResult.parentSymbol,
+ identOrOp=lookupResult.identOrOp,
+ templateParams=lookupResult.templateParams,
+ templateArgs=lookupResult.templateArgs,
+ declaration=declaration,
+ docname=docname, line=line)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("end: creating candidate symbol")
+ return symbol
+ if len(withDecl) == 0:
+ candSymbol = None
+ else:
+ candSymbol = makeCandSymbol()
+
+ def handleDuplicateDeclaration(symbol: Symbol, candSymbol: Symbol) -> None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("redeclaration")
+ Symbol.debug_indent -= 1
+ Symbol.debug_indent -= 2
+ # Redeclaration of the same symbol.
+ # Let the new one be there, but raise an error to the client
+ # so it can use the real symbol as subscope.
+ # This will probably result in a duplicate id warning.
+ candSymbol.isRedeclaration = True
+ raise _DuplicateSymbolError(symbol, declaration)
+
+ if declaration.objectType != "function":
+ assert len(withDecl) <= 1
+ handleDuplicateDeclaration(withDecl[0], candSymbol)
+ # (not reachable)
+
+ # a function, so compare IDs
+ candId = declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candId:", candId)
+ for symbol in withDecl:
+ # but all existing must be functions as well,
+ # otherwise we declare it to be a duplicate
+ if symbol.declaration.objectType != 'function':
+ handleDuplicateDeclaration(symbol, candSymbol)
+ # (not reachable)
+ oldId = symbol.declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("oldId: ", oldId)
+ if candId == oldId:
+ handleDuplicateDeclaration(symbol, candSymbol)
+ # (not reachable)
+ # no candidate symbol found with matching ID
+ # if there is an empty symbol, fill that one
+ if len(noDecl) == 0:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("no match, no empty")
+ if candSymbol is not None:
+ Symbol.debug_print("result is already created candSymbol")
+ else:
+ Symbol.debug_print("result is makeCandSymbol()")
+ Symbol.debug_indent -= 2
+ if candSymbol is not None:
+ return candSymbol
+ else:
+ return makeCandSymbol()
+ else:
+ if Symbol.debug_lookup:
+ Symbol.debug_print(
+ "no match, but fill an empty declaration, candSybmol is not None?:",
+ candSymbol is not None,
+ )
+ Symbol.debug_indent -= 2
+ if candSymbol is not None:
+ candSymbol.remove()
+ # assert len(noDecl) == 1
+ # TODO: enable assertion when we at some point find out how to do cleanup
+ # for now, just take the first one, it should work fine ... right?
+ symbol = noDecl[0]
+ # If someone first opened the scope, and then later
+ # declares it, e.g,
+ # .. namespace:: Test
+ # .. namespace:: nullptr
+ # .. class:: Test
+ symbol._fill_empty(declaration, docname, line)
+ return symbol
+
+ def merge_with(self, other: Symbol, docnames: list[str],
+ env: BuildEnvironment) -> None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("merge_with:")
+ assert other is not None
+
+ def unconditionalAdd(self: Symbol, otherChild: Symbol) -> None:
+ # TODO: hmm, should we prune by docnames?
+ self._children.append(otherChild)
+ otherChild.parent = self
+ otherChild._assert_invariants()
+
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ for otherChild in other._children:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("otherChild:\n", otherChild.to_string(Symbol.debug_indent))
+ Symbol.debug_indent += 1
+ if otherChild.isRedeclaration:
+ unconditionalAdd(self, otherChild)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("isRedeclaration")
+ Symbol.debug_indent -= 1
+ continue
+ candiateIter = self._find_named_symbols(
+ identOrOp=otherChild.identOrOp,
+ templateParams=otherChild.templateParams,
+ templateArgs=otherChild.templateArgs,
+ templateShorthand=False, matchSelf=False,
+ recurseInAnon=False, correctPrimaryTemplateArgs=False,
+ searchInSiblings=False)
+ candidates = list(candiateIter)
+
+ if Symbol.debug_lookup:
+ Symbol.debug_print("raw candidate symbols:", len(candidates))
+ symbols = [s for s in candidates if not s.isRedeclaration]
+ if Symbol.debug_lookup:
+ Symbol.debug_print("non-duplicate candidate symbols:", len(symbols))
+
+ if len(symbols) == 0:
+ unconditionalAdd(self, otherChild)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ continue
+
+ ourChild = None
+ if otherChild.declaration is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("no declaration in other child")
+ ourChild = symbols[0]
+ else:
+ queryId = otherChild.declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("queryId: ", queryId)
+ for symbol in symbols:
+ if symbol.declaration is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_print("empty candidate")
+ # if in the end we have non-matching, but have an empty one,
+ # then just continue with that
+ ourChild = symbol
+ continue
+ candId = symbol.declaration.get_newest_id()
+ if Symbol.debug_lookup:
+ Symbol.debug_print("candidate:", candId)
+ if candId == queryId:
+ ourChild = symbol
+ break
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ if ourChild is None:
+ unconditionalAdd(self, otherChild)
+ continue
+ if otherChild.declaration and otherChild.docname in docnames:
+ if not ourChild.declaration:
+ ourChild._fill_empty(otherChild.declaration,
+ otherChild.docname, otherChild.line)
+ elif ourChild.docname != otherChild.docname:
+ name = str(ourChild.declaration)
+ msg = __("Duplicate C++ declaration, also defined at %s:%s.\n"
+ "Declaration is '.. cpp:%s:: %s'.")
+ msg = msg % (ourChild.docname, ourChild.line,
+ ourChild.declaration.directiveType, name)
+ logger.warning(msg, location=(otherChild.docname, otherChild.line))
+ else:
+ if (otherChild.declaration.objectType ==
+ ourChild.declaration.objectType and
+ otherChild.declaration.objectType in
+ ('templateParam', 'functionParam') and
+ ourChild.parent.declaration == otherChild.parent.declaration):
+ # `ourChild` was just created during merging by the call
+ # to `_fill_empty` on the parent and can be ignored.
+ pass
+ else:
+ # Both have declarations, and in the same docname.
+ # This can apparently happen, it should be safe to
+ # just ignore it, right?
+ # Hmm, only on duplicate declarations, right?
+ msg = "Internal C++ domain error during symbol merging.\n"
+ msg += "ourChild:\n" + ourChild.to_string(1)
+ msg += "\notherChild:\n" + otherChild.to_string(1)
+ logger.warning(msg, location=otherChild.docname)
+ ourChild.merge_with(otherChild, docnames, env)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+
+ def add_name(self, nestedName: ASTNestedName,
+ templatePrefix: ASTTemplateDeclarationPrefix | None = None) -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("add_name:")
+ if templatePrefix:
+ templateDecls = templatePrefix.templates
+ else:
+ templateDecls = []
+ res = self._add_symbols(nestedName, templateDecls,
+ declaration=None, docname=None, line=None)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ return res
+
+ def add_declaration(self, declaration: ASTDeclaration,
+ docname: str, line: int) -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("add_declaration:")
+ assert declaration is not None
+ assert docname is not None
+ assert line is not None
+ nestedName = declaration.name
+ if declaration.templatePrefix:
+ templateDecls = declaration.templatePrefix.templates
+ else:
+ templateDecls = []
+ res = self._add_symbols(nestedName, templateDecls, declaration, docname, line)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ return res
+
+ def find_identifier(self, identOrOp: ASTIdentifier | ASTOperator,
+ matchSelf: bool, recurseInAnon: bool, searchInSiblings: bool,
+ ) -> Symbol | None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_identifier:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("identOrOp: ", identOrOp)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings:", searchInSiblings)
+ logger.debug(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_indent -= 2
+ current = self
+ while current is not None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 2
+ Symbol.debug_print("trying:")
+ logger.debug(current.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_indent -= 2
+ if matchSelf and current.identOrOp == identOrOp:
+ return current
+ children = current.children_recurse_anon if recurseInAnon else current._children
+ for s in children:
+ if s.identOrOp == identOrOp:
+ return s
+ if not searchInSiblings:
+ break
+ current = current.siblingAbove
+ return None
+
+ def direct_lookup(self, key: LookupKey) -> Symbol:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("direct_lookup:")
+ Symbol.debug_indent += 1
+ s = self
+ for name, templateParams, id_ in key.data:
+ if id_ is not None:
+ res = None
+ for cand in s._children:
+ if cand.declaration is None:
+ continue
+ if cand.declaration.get_newest_id() == id_:
+ res = cand
+ break
+ s = res
+ else:
+ identOrOp = name.identOrOp
+ templateArgs = name.templateArgs
+ s = s._find_first_named_symbol(identOrOp,
+ templateParams, templateArgs,
+ templateShorthand=False,
+ matchSelf=False,
+ recurseInAnon=False,
+ correctPrimaryTemplateArgs=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_print("name: ", name)
+ Symbol.debug_print("templateParams:", templateParams)
+ Symbol.debug_print("id: ", id_)
+ if s is not None:
+ logger.debug(s.to_string(Symbol.debug_indent + 1), end="")
+ else:
+ Symbol.debug_print("not found")
+ if s is None:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return None
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return s
+
+ def find_name(
+ self,
+ nestedName: ASTNestedName,
+ templateDecls: list[Any],
+ typ: str,
+ templateShorthand: bool,
+ matchSelf: bool,
+ recurseInAnon: bool,
+ searchInSiblings: bool,
+ ) -> tuple[list[Symbol] | None, str]:
+ # templateShorthand: missing template parameter lists for templates is ok
+ # If the first component is None,
+ # then the second component _may_ be a string explaining why.
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_name:")
+ Symbol.debug_indent += 1
+ Symbol.debug_print("self:")
+ logger.debug(self.to_string(Symbol.debug_indent + 1), end="")
+ Symbol.debug_print("nestedName: ", nestedName)
+ Symbol.debug_print("templateDecls: ", templateDecls)
+ Symbol.debug_print("typ: ", typ)
+ Symbol.debug_print("templateShorthand:", templateShorthand)
+ Symbol.debug_print("matchSelf: ", matchSelf)
+ Symbol.debug_print("recurseInAnon: ", recurseInAnon)
+ Symbol.debug_print("searchInSiblings: ", searchInSiblings)
+
+ class QualifiedSymbolIsTemplateParam(Exception):
+ pass
+
+ def onMissingQualifiedSymbol(parentSymbol: Symbol,
+ identOrOp: ASTIdentifier | ASTOperator,
+ templateParams: Any,
+ templateArgs: ASTTemplateArgs) -> Symbol | None:
+ # TODO: Maybe search without template args?
+ # Though, the correctPrimaryTemplateArgs does
+ # that for primary templates.
+ # Is there another case where it would be good?
+ if parentSymbol.declaration is not None:
+ if parentSymbol.declaration.objectType == 'templateParam':
+ raise QualifiedSymbolIsTemplateParam
+ return None
+
+ try:
+ lookupResult = self._symbol_lookup(nestedName, templateDecls,
+ onMissingQualifiedSymbol,
+ strictTemplateParamArgLists=False,
+ ancestorLookupType=typ,
+ templateShorthand=templateShorthand,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ correctPrimaryTemplateArgs=False,
+ searchInSiblings=searchInSiblings)
+ except QualifiedSymbolIsTemplateParam:
+ return None, "templateParamInQualified"
+
+ if lookupResult is None:
+ # if it was a part of the qualification that could not be found
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return None, None
+
+ res = list(lookupResult.symbols)
+ if len(res) != 0:
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ return res, None
+
+ if lookupResult.parentSymbol.declaration is not None:
+ if lookupResult.parentSymbol.declaration.objectType == 'templateParam':
+ return None, "templateParamInQualified"
+
+ # try without template params and args
+ symbol = lookupResult.parentSymbol._find_first_named_symbol(
+ lookupResult.identOrOp, None, None,
+ templateShorthand=templateShorthand, matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon, correctPrimaryTemplateArgs=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 2
+ if symbol is not None:
+ return [symbol], None
+ else:
+ return None, None
+
+ def find_declaration(self, declaration: ASTDeclaration, typ: str, templateShorthand: bool,
+ matchSelf: bool, recurseInAnon: bool) -> Symbol | None:
+ # templateShorthand: missing template parameter lists for templates is ok
+ if Symbol.debug_lookup:
+ Symbol.debug_indent += 1
+ Symbol.debug_print("find_declaration:")
+ nestedName = declaration.name
+ if declaration.templatePrefix:
+ templateDecls = declaration.templatePrefix.templates
+ else:
+ templateDecls = []
+
+ def onMissingQualifiedSymbol(parentSymbol: Symbol,
+ identOrOp: ASTIdentifier | ASTOperator,
+ templateParams: Any,
+ templateArgs: ASTTemplateArgs) -> Symbol | None:
+ return None
+
+ lookupResult = self._symbol_lookup(nestedName, templateDecls,
+ onMissingQualifiedSymbol,
+ strictTemplateParamArgLists=False,
+ ancestorLookupType=typ,
+ templateShorthand=templateShorthand,
+ matchSelf=matchSelf,
+ recurseInAnon=recurseInAnon,
+ correctPrimaryTemplateArgs=False,
+ searchInSiblings=False)
+ if Symbol.debug_lookup:
+ Symbol.debug_indent -= 1
+ if lookupResult is None:
+ return None
+
+ symbols = list(lookupResult.symbols)
+ if len(symbols) == 0:
+ return None
+
+ querySymbol = Symbol(parent=lookupResult.parentSymbol,
+ identOrOp=lookupResult.identOrOp,
+ templateParams=lookupResult.templateParams,
+ templateArgs=lookupResult.templateArgs,
+ declaration=declaration,
+ docname='fakeDocnameForQuery',
+ line=42)
+ queryId = declaration.get_newest_id()
+ for symbol in symbols:
+ if symbol.declaration is None:
+ continue
+ candId = symbol.declaration.get_newest_id()
+ if candId == queryId:
+ querySymbol.remove()
+ return symbol
+ querySymbol.remove()
+ return None
+
+ def to_string(self, indent: int) -> str:
+ res = [Symbol.debug_indent_string * indent]
+ if not self.parent:
+ res.append('::')
+ else:
+ if self.templateParams:
+ res.append(str(self.templateParams))
+ res.append('\n')
+ res.append(Symbol.debug_indent_string * indent)
+ if self.identOrOp:
+ res.append(str(self.identOrOp))
+ else:
+ res.append(str(self.declaration))
+ if self.templateArgs:
+ res.append(str(self.templateArgs))
+ if self.declaration:
+ res.append(": ")
+ if self.isRedeclaration:
+ res.append('!!duplicate!! ')
+ res.append("{" + self.declaration.objectType + "} ")
+ res.append(str(self.declaration))
+ if self.docname:
+ res.append('\t(')
+ res.append(self.docname)
+ res.append(')')
+ res.append('\n')
+ return ''.join(res)
+
+ def dump(self, indent: int) -> str:
+ return ''.join([
+ self.to_string(indent),
+ *(c.dump(indent + 1) for c in self._children),
+ ])
diff --git a/sphinx/domains/index.py b/sphinx/domains/index.py
index c084516..87d1cac 100644
--- a/sphinx/domains/index.py
+++ b/sphinx/domains/index.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, Any
+from typing import TYPE_CHECKING, Any, ClassVar
from docutils import nodes
from docutils.parsers.rst import directives
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
logger = logging.getLogger(__name__)
@@ -29,6 +29,7 @@ logger = logging.getLogger(__name__)
class IndexDomain(Domain):
"""Mathematics domain."""
+
name = 'index'
label = 'index'
@@ -62,11 +63,12 @@ class IndexDirective(SphinxDirective):
"""
Directive to add entries to the index.
"""
+
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
- option_spec: OptionSpec = {
+ option_spec: ClassVar[OptionSpec] = {
'name': directives.unchanged,
}
@@ -113,7 +115,7 @@ class IndexRole(ReferenceRole):
return [index, target, text], []
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(IndexDomain)
app.add_directive('index', IndexDirective)
app.add_role('index', IndexRole())
diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py
index 75149c3..9b881f8 100644
--- a/sphinx/domains/javascript.py
+++ b/sphinx/domains/javascript.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import contextlib
-from typing import TYPE_CHECKING, Any, cast
+from typing import TYPE_CHECKING, Any, ClassVar, cast
from docutils import nodes
from docutils.parsers.rst import directives
@@ -11,7 +11,7 @@ from docutils.parsers.rst import directives
from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
-from sphinx.domains.python import _pseudo_parse_arglist
+from sphinx.domains.python._annotations import _pseudo_parse_arglist
from sphinx.locale import _, __
from sphinx.roles import XRefRole
from sphinx.util import logging
@@ -28,7 +28,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
logger = logging.getLogger(__name__)
@@ -37,6 +37,7 @@ class JSObject(ObjectDescription[tuple[str, str]]):
"""
Description of a JavaScript object.
"""
+
#: If set to ``True`` this object is callable and a `desc_parameterlist` is
#: added
has_arguments = False
@@ -45,7 +46,7 @@ class JSObject(ObjectDescription[tuple[str, str]]):
#: based on directive nesting
allow_nesting = False
- option_spec: OptionSpec = {
+ option_spec: ClassVar[OptionSpec] = {
'no-index': directives.flag,
'no-index-entry': directives.flag,
'no-contents-entry': directives.flag,
@@ -88,12 +89,12 @@ class JSObject(ObjectDescription[tuple[str, str]]):
finally:
name = member_name
if prefix and member_prefix:
- prefix = '.'.join([prefix, member_prefix])
+ prefix = f'{prefix}.{member_prefix}'
elif prefix is None and member_prefix:
prefix = member_prefix
fullname = name
if prefix:
- fullname = '.'.join([prefix, name])
+ fullname = f'{prefix}.{name}'
signode['module'] = mod_name
signode['object'] = prefix
@@ -241,12 +242,13 @@ class JSObject(ObjectDescription[tuple[str, str]]):
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 '.'.join([*parents, name + parens])
return ''
class JSCallable(JSObject):
"""Description of a JavaScript function, method or constructor."""
+
has_arguments = True
doc_field_types = [
@@ -296,7 +298,7 @@ class JSModule(SphinxDirective):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = False
- option_spec: OptionSpec = {
+ option_spec: ClassVar[OptionSpec] = {
'no-index': directives.flag,
'no-contents-entry': directives.flag,
'no-typesetting': directives.flag,
@@ -358,6 +360,7 @@ class JSXRefRole(XRefRole):
class JavaScriptDomain(Domain):
"""JavaScript language domain."""
+
name = 'js'
label = 'JavaScript'
# if you add a new object type make sure to edit JSObject.get_index_string
@@ -440,11 +443,11 @@ class JavaScriptDomain(Domain):
searches = []
if mod_name and prefix:
- searches.append('.'.join([mod_name, prefix, name]))
+ searches.append(f'{mod_name}.{prefix}.{name}')
if mod_name:
- searches.append('.'.join([mod_name, name]))
+ searches.append(f'{mod_name}.{name}')
if prefix:
- searches.append('.'.join([prefix, name]))
+ searches.append(f'{prefix}.{name}')
searches.append(name)
if searchorder == 0:
@@ -495,10 +498,10 @@ class JavaScriptDomain(Domain):
return '.'.join(filter(None, [modname, prefix, target]))
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(JavaScriptDomain)
app.add_config_value(
- 'javascript_maximum_signature_line_length', None, 'env', types={int, None},
+ 'javascript_maximum_signature_line_length', None, 'env', {int, type(None)},
)
return {
'version': 'builtin',
diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py
index d283d3f..708a4d5 100644
--- a/sphinx/domains/math.py
+++ b/sphinx/domains/math.py
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import ExtensionMetadata
logger = logging.getLogger(__name__)
@@ -34,6 +35,7 @@ class MathReferenceRole(XRefRole):
class MathDomain(Domain):
"""Mathematics domain."""
+
name = 'math'
label = 'mathematics'
@@ -134,13 +136,16 @@ class MathDomain(Domain):
return []
def has_equations(self, docname: str | None = None) -> bool:
- if docname:
- return self.data['has_equations'].get(docname, False)
- else:
+ if not docname:
return any(self.data['has_equations'].values())
+ return (
+ self.data['has_equations'].get(docname, False)
+ or any(map(self.has_equations, self.env.toctree_includes.get(docname, ())))
+ )
+
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(MathDomain)
app.add_role('eq', MathReferenceRole(warn_dangling=True))
diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py
deleted file mode 100644
index 930f8e7..0000000
--- a/sphinx/domains/python.py
+++ /dev/null
@@ -1,1769 +0,0 @@
-"""The Python domain."""
-
-from __future__ import annotations
-
-import ast
-import builtins
-import contextlib
-import inspect
-import re
-import token
-import typing
-from inspect import Parameter
-from typing import TYPE_CHECKING, Any, NamedTuple, cast
-
-from docutils import nodes
-from docutils.parsers.rst import directives
-
-from sphinx import addnodes
-from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
-from sphinx.directives import ObjectDescription
-from sphinx.domains import Domain, Index, IndexEntry, ObjType
-from sphinx.locale import _, __
-from sphinx.pycode.parser import Token, TokenProcessor
-from sphinx.roles import XRefRole
-from sphinx.util import logging
-from sphinx.util.docfields import Field, GroupedField, TypedField
-from sphinx.util.docutils import SphinxDirective
-from sphinx.util.inspect import signature_from_str
-from sphinx.util.nodes import (
- find_pending_xref_condition,
- make_id,
- make_refnode,
- nested_parse_with_titles,
-)
-
-if TYPE_CHECKING:
- from collections.abc import Iterable, Iterator
-
- from docutils.nodes import Element, Node
- from docutils.parsers.rst.states import Inliner
-
- from sphinx.application import Sphinx
- from sphinx.builders import Builder
- from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec, TextlikeNode
-
-logger = logging.getLogger(__name__)
-
-
-# REs for Python signatures
-py_sig_re = re.compile(
- r'''^ ([\w.]*\.)? # class name(s)
- (\w+) \s* # thing name
- (?: \[\s*(.*)\s*])? # optional: type parameters list
- (?: \(\s*(.*)\s*\) # optional: arguments
- (?:\s* -> \s* (.*))? # return annotation
- )? $ # and nothing more
- ''', re.VERBOSE)
-
-
-pairindextypes = {
- 'module': 'module',
- 'keyword': 'keyword',
- 'operator': 'operator',
- 'object': 'object',
- 'exception': 'exception',
- 'statement': 'statement',
- 'builtin': 'built-in function',
-}
-
-
-class ObjectEntry(NamedTuple):
- docname: str
- node_id: str
- objtype: str
- aliased: bool
-
-
-class ModuleEntry(NamedTuple):
- docname: str
- node_id: str
- synopsis: str
- platform: str
- deprecated: bool
-
-
-def parse_reftarget(reftarget: str, suppress_prefix: bool = False,
- ) -> tuple[str, str, str, bool]:
- """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
- refspecific = False
- if reftarget.startswith('.'):
- reftarget = reftarget[1:]
- title = reftarget
- refspecific = True
- elif reftarget.startswith('~'):
- reftarget = reftarget[1:]
- title = reftarget.split('.')[-1]
- elif suppress_prefix:
- title = reftarget.split('.')[-1]
- elif reftarget.startswith('typing.'):
- title = reftarget[7:]
- else:
- title = reftarget
-
- if reftarget == 'None' or reftarget.startswith('typing.'):
- # typing module provides non-class types. Obj reference is good to refer them.
- reftype = 'obj'
- else:
- reftype = 'class'
-
- return reftype, reftarget, title, refspecific
-
-
-def type_to_xref(target: str, env: BuildEnvironment, *,
- suppress_prefix: bool = False) -> addnodes.pending_xref:
- """Convert a type string to a cross reference node."""
- if env:
- kwargs = {'py:module': env.ref_context.get('py:module'),
- 'py:class': env.ref_context.get('py:class')}
- else:
- kwargs = {}
-
- reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
-
- if env.config.python_use_unqualified_type_names:
- # Note: It would be better to use qualname to describe the object to support support
- # nested classes. But python domain can't access the real python object because this
- # module should work not-dynamically.
- shortname = title.split('.')[-1]
- contnodes: list[Node] = [pending_xref_condition('', shortname, condition='resolved'),
- pending_xref_condition('', title, condition='*')]
- else:
- contnodes = [nodes.Text(title)]
-
- return pending_xref('', *contnodes,
- refdomain='py', reftype=reftype, reftarget=target,
- refspecific=refspecific, **kwargs)
-
-
-def _parse_annotation(annotation: str, env: BuildEnvironment) -> list[Node]:
- """Parse type annotation."""
- short_literals = env.config.python_display_short_literal_types
-
- def unparse(node: ast.AST) -> list[Node]:
- if isinstance(node, ast.Attribute):
- return [nodes.Text(f"{unparse(node.value)[0]}.{node.attr}")]
- if isinstance(node, ast.BinOp):
- result: list[Node] = unparse(node.left)
- result.extend(unparse(node.op))
- result.extend(unparse(node.right))
- return result
- if isinstance(node, ast.BitOr):
- return [addnodes.desc_sig_space(),
- addnodes.desc_sig_punctuation('', '|'),
- addnodes.desc_sig_space()]
- if isinstance(node, ast.Constant):
- if node.value is Ellipsis:
- return [addnodes.desc_sig_punctuation('', "...")]
- if isinstance(node.value, bool):
- return [addnodes.desc_sig_keyword('', repr(node.value))]
- if isinstance(node.value, int):
- return [addnodes.desc_sig_literal_number('', repr(node.value))]
- if isinstance(node.value, str):
- return [addnodes.desc_sig_literal_string('', repr(node.value))]
- else:
- # handles None, which is further handled by type_to_xref later
- # and fallback for other types that should be converted
- return [nodes.Text(repr(node.value))]
- if isinstance(node, ast.Expr):
- return unparse(node.value)
- if isinstance(node, ast.Invert):
- return [addnodes.desc_sig_punctuation('', '~')]
- if isinstance(node, ast.List):
- result = [addnodes.desc_sig_punctuation('', '[')]
- if node.elts:
- # check if there are elements in node.elts to only pop the
- # last element of result if the for-loop was run at least
- # once
- for elem in node.elts:
- result.extend(unparse(elem))
- result.append(addnodes.desc_sig_punctuation('', ','))
- result.append(addnodes.desc_sig_space())
- result.pop()
- result.pop()
- result.append(addnodes.desc_sig_punctuation('', ']'))
- return result
- if isinstance(node, ast.Module):
- return sum((unparse(e) for e in node.body), [])
- if isinstance(node, ast.Name):
- return [nodes.Text(node.id)]
- if isinstance(node, ast.Subscript):
- if getattr(node.value, 'id', '') in {'Optional', 'Union'}:
- return _unparse_pep_604_annotation(node)
- if short_literals and getattr(node.value, 'id', '') == 'Literal':
- return _unparse_pep_604_annotation(node)
- result = unparse(node.value)
- result.append(addnodes.desc_sig_punctuation('', '['))
- result.extend(unparse(node.slice))
- result.append(addnodes.desc_sig_punctuation('', ']'))
-
- # Wrap the Text nodes inside brackets by literal node if the subscript is a Literal
- if result[0] in ('Literal', 'typing.Literal'):
- for i, subnode in enumerate(result[1:], start=1):
- if isinstance(subnode, nodes.Text):
- result[i] = nodes.literal('', '', subnode)
- return result
- if isinstance(node, ast.UnaryOp):
- return unparse(node.op) + unparse(node.operand)
- if isinstance(node, ast.Tuple):
- if node.elts:
- result = []
- for elem in node.elts:
- result.extend(unparse(elem))
- result.append(addnodes.desc_sig_punctuation('', ','))
- result.append(addnodes.desc_sig_space())
- result.pop()
- result.pop()
- else:
- result = [addnodes.desc_sig_punctuation('', '('),
- addnodes.desc_sig_punctuation('', ')')]
-
- return result
- raise SyntaxError # unsupported syntax
-
- def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
- subscript = node.slice
-
- flattened: list[Node] = []
- if isinstance(subscript, ast.Tuple):
- flattened.extend(unparse(subscript.elts[0]))
- for elt in subscript.elts[1:]:
- flattened.extend(unparse(ast.BitOr()))
- flattened.extend(unparse(elt))
- else:
- # e.g. a Union[] inside an Optional[]
- flattened.extend(unparse(subscript))
-
- if getattr(node.value, 'id', '') == 'Optional':
- flattened.extend(unparse(ast.BitOr()))
- flattened.append(nodes.Text('None'))
-
- return flattened
-
- try:
- tree = ast.parse(annotation, type_comments=True)
- result: list[Node] = []
- for node in unparse(tree):
- if isinstance(node, nodes.literal):
- result.append(node[0])
- elif isinstance(node, nodes.Text) and node.strip():
- if (result and isinstance(result[-1], addnodes.desc_sig_punctuation) and
- result[-1].astext() == '~'):
- result.pop()
- result.append(type_to_xref(str(node), env, suppress_prefix=True))
- else:
- result.append(type_to_xref(str(node), env))
- else:
- result.append(node)
- return result
- except SyntaxError:
- return [type_to_xref(annotation, env)]
-
-
-class _TypeParameterListParser(TokenProcessor):
- def __init__(self, sig: str) -> None:
- signature = sig.replace('\n', '').strip()
- super().__init__([signature])
- # Each item is a tuple (name, kind, default, annotation) mimicking
- # ``inspect.Parameter`` to allow default values on VAR_POSITIONAL
- # or VAR_KEYWORD parameters.
- self.type_params: list[tuple[str, int, Any, Any]] = []
-
- def fetch_type_param_spec(self) -> list[Token]:
- tokens = []
- while current := self.fetch_token():
- tokens.append(current)
- for ldelim, rdelim in ('(', ')'), ('{', '}'), ('[', ']'):
- if current == [token.OP, ldelim]:
- tokens += self.fetch_until([token.OP, rdelim])
- break
- else:
- if current == token.INDENT:
- tokens += self.fetch_until(token.DEDENT)
- elif current.match(
- [token.OP, ':'], [token.OP, '='], [token.OP, ',']):
- tokens.pop()
- break
- return tokens
-
- def parse(self) -> None:
- while current := self.fetch_token():
- if current == token.NAME:
- tp_name = current.value.strip()
- if self.previous and self.previous.match([token.OP, '*'], [token.OP, '**']):
- if self.previous == [token.OP, '*']:
- tp_kind = Parameter.VAR_POSITIONAL
- else:
- tp_kind = Parameter.VAR_KEYWORD # type: ignore[assignment]
- else:
- tp_kind = Parameter.POSITIONAL_OR_KEYWORD # type: ignore[assignment]
-
- tp_ann: Any = Parameter.empty
- tp_default: Any = Parameter.empty
-
- current = self.fetch_token()
- if current and current.match([token.OP, ':'], [token.OP, '=']):
- if current == [token.OP, ':']:
- tokens = self.fetch_type_param_spec()
- tp_ann = self._build_identifier(tokens)
-
- if self.current and self.current == [token.OP, '=']:
- tokens = self.fetch_type_param_spec()
- tp_default = self._build_identifier(tokens)
-
- if tp_kind != Parameter.POSITIONAL_OR_KEYWORD and tp_ann != Parameter.empty:
- msg = ('type parameter bound or constraint is not allowed '
- f'for {tp_kind.description} parameters')
- raise SyntaxError(msg)
-
- type_param = (tp_name, tp_kind, tp_default, tp_ann)
- self.type_params.append(type_param)
-
- def _build_identifier(self, tokens: list[Token]) -> str:
- from itertools import chain, tee
-
- def pairwise(iterable):
- a, b = tee(iterable)
- next(b, None)
- return zip(a, b)
-
- def triplewise(iterable):
- for (a, _z), (b, c) in pairwise(pairwise(iterable)):
- yield a, b, c
-
- idents: list[str] = []
- tokens: Iterable[Token] = iter(tokens) # type: ignore[no-redef]
- # do not format opening brackets
- for tok in tokens:
- if not tok.match([token.OP, '('], [token.OP, '['], [token.OP, '{']):
- # check if the first non-delimiter character is an unpack operator
- is_unpack_operator = tok.match([token.OP, '*'], [token.OP, ['**']])
- idents.append(self._pformat_token(tok, native=is_unpack_operator))
- break
- idents.append(tok.value)
-
- # check the remaining tokens
- stop = Token(token.ENDMARKER, '', (-1, -1), (-1, -1), '<sentinel>')
- is_unpack_operator = False
- for tok, op, after in triplewise(chain(tokens, [stop, stop])):
- ident = self._pformat_token(tok, native=is_unpack_operator)
- idents.append(ident)
- # determine if the next token is an unpack operator depending
- # on the left and right hand side of the operator symbol
- is_unpack_operator = (
- op.match([token.OP, '*'], [token.OP, '**']) and not (
- tok.match(token.NAME, token.NUMBER, token.STRING,
- [token.OP, ')'], [token.OP, ']'], [token.OP, '}'])
- and after.match(token.NAME, token.NUMBER, token.STRING,
- [token.OP, '('], [token.OP, '['], [token.OP, '{'])
- )
- )
-
- return ''.join(idents).strip()
-
- def _pformat_token(self, tok: Token, native: bool = False) -> str:
- if native:
- return tok.value
-
- if tok.match(token.NEWLINE, token.ENDMARKER):
- return ''
-
- if tok.match([token.OP, ':'], [token.OP, ','], [token.OP, '#']):
- return f'{tok.value} '
-
- # Arithmetic operators are allowed because PEP 695 specifies the
- # default type parameter to be *any* expression (so "T1 << T2" is
- # allowed if it makes sense). The caller is responsible to ensure
- # that a multiplication operator ("*") is not to be confused with
- # an unpack operator (which will not be surrounded by spaces).
- #
- # The operators are ordered according to how likely they are to
- # be used and for (possible) future implementations (e.g., "&" for
- # an intersection type).
- if tok.match(
- # Most likely operators to appear
- [token.OP, '='], [token.OP, '|'],
- # Type composition (future compatibility)
- [token.OP, '&'], [token.OP, '^'], [token.OP, '<'], [token.OP, '>'],
- # Unlikely type composition
- [token.OP, '+'], [token.OP, '-'], [token.OP, '*'], [token.OP, '**'],
- # Unlikely operators but included for completeness
- [token.OP, '@'], [token.OP, '/'], [token.OP, '//'], [token.OP, '%'],
- [token.OP, '<<'], [token.OP, '>>'], [token.OP, '>>>'],
- [token.OP, '<='], [token.OP, '>='], [token.OP, '=='], [token.OP, '!='],
- ):
- return f' {tok.value} '
-
- return tok.value
-
-
-def _parse_type_list(
- tp_list: str, env: BuildEnvironment,
- multi_line_parameter_list: bool = False,
-) -> addnodes.desc_type_parameter_list:
- """Parse a list of type parameters according to PEP 695."""
- type_params = addnodes.desc_type_parameter_list(tp_list)
- type_params['multi_line_parameter_list'] = multi_line_parameter_list
- # formal parameter names are interpreted as type parameter names and
- # type annotations are interpreted as type parameter bound or constraints
- parser = _TypeParameterListParser(tp_list)
- parser.parse()
- for (tp_name, tp_kind, tp_default, tp_ann) in parser.type_params:
- # no positional-only or keyword-only allowed in a type parameters list
- if tp_kind in {Parameter.POSITIONAL_ONLY, Parameter.KEYWORD_ONLY}:
- msg = ('positional-only or keyword-only parameters '
- 'are prohibited in type parameter lists')
- raise SyntaxError(msg)
-
- node = addnodes.desc_type_parameter()
- if tp_kind == Parameter.VAR_POSITIONAL:
- node += addnodes.desc_sig_operator('', '*')
- elif tp_kind == Parameter.VAR_KEYWORD:
- node += addnodes.desc_sig_operator('', '**')
- node += addnodes.desc_sig_name('', tp_name)
-
- if tp_ann is not Parameter.empty:
- annotation = _parse_annotation(tp_ann, env)
- if not annotation:
- continue
-
- node += addnodes.desc_sig_punctuation('', ':')
- node += addnodes.desc_sig_space()
-
- type_ann_expr = addnodes.desc_sig_name('', '',
- *annotation) # type: ignore[arg-type]
- # a type bound is ``T: U`` whereas type constraints
- # must be enclosed with parentheses. ``T: (U, V)``
- if tp_ann.startswith('(') and tp_ann.endswith(')'):
- type_ann_text = type_ann_expr.astext()
- if type_ann_text.startswith('(') and type_ann_text.endswith(')'):
- node += type_ann_expr
- else:
- # surrounding braces are lost when using _parse_annotation()
- node += addnodes.desc_sig_punctuation('', '(')
- node += type_ann_expr # type constraint
- node += addnodes.desc_sig_punctuation('', ')')
- else:
- node += type_ann_expr # type bound
-
- if tp_default is not Parameter.empty:
- # Always surround '=' with spaces, even if there is no annotation
- node += addnodes.desc_sig_space()
- node += addnodes.desc_sig_operator('', '=')
- node += addnodes.desc_sig_space()
- node += nodes.inline('', tp_default,
- classes=['default_value'],
- support_smartquotes=False)
-
- type_params += node
- return type_params
-
-
-def _parse_arglist(
- arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False,
-) -> addnodes.desc_parameterlist:
- """Parse a list of arguments using AST parser"""
- params = addnodes.desc_parameterlist(arglist)
- params['multi_line_parameter_list'] = multi_line_parameter_list
- sig = signature_from_str('(%s)' % arglist)
- last_kind = None
- for param in sig.parameters.values():
- if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
- # PEP-570: Separator for Positional Only Parameter: /
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
- if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
- param.POSITIONAL_ONLY,
- None):
- # PEP-3102: Separator for Keyword Only Parameter: *
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '*'))
-
- node = addnodes.desc_parameter()
- if param.kind == param.VAR_POSITIONAL:
- node += addnodes.desc_sig_operator('', '*')
- node += addnodes.desc_sig_name('', param.name)
- elif param.kind == param.VAR_KEYWORD:
- node += addnodes.desc_sig_operator('', '**')
- node += addnodes.desc_sig_name('', param.name)
- else:
- node += addnodes.desc_sig_name('', param.name)
-
- if param.annotation is not param.empty:
- children = _parse_annotation(param.annotation, env)
- node += addnodes.desc_sig_punctuation('', ':')
- node += addnodes.desc_sig_space()
- node += addnodes.desc_sig_name('', '', *children) # type: ignore[arg-type]
- if param.default is not param.empty:
- if param.annotation is not param.empty:
- node += addnodes.desc_sig_space()
- node += addnodes.desc_sig_operator('', '=')
- node += addnodes.desc_sig_space()
- else:
- node += addnodes.desc_sig_operator('', '=')
- node += nodes.inline('', param.default, classes=['default_value'],
- support_smartquotes=False)
-
- params += node
- last_kind = param.kind
-
- if last_kind == Parameter.POSITIONAL_ONLY:
- # PEP-570: Separator for Positional Only Parameter: /
- params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
-
- return params
-
-
-def _pseudo_parse_arglist(
- signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False,
-) -> None:
- """"Parse" a list of arguments separated by commas.
-
- Arguments can have "optional" annotations given by enclosing them in
- brackets. Currently, this will split at any comma, even if it's inside a
- string literal (e.g. default argument value).
- """
- paramlist = addnodes.desc_parameterlist()
- paramlist['multi_line_parameter_list'] = multi_line_parameter_list
- stack: list[Element] = [paramlist]
- try:
- for argument in arglist.split(','):
- argument = argument.strip()
- ends_open = ends_close = 0
- while argument.startswith('['):
- stack.append(addnodes.desc_optional())
- stack[-2] += stack[-1]
- argument = argument[1:].strip()
- while argument.startswith(']'):
- stack.pop()
- argument = argument[1:].strip()
- while argument.endswith(']') and not argument.endswith('[]'):
- ends_close += 1
- argument = argument[:-1].strip()
- while argument.endswith('['):
- ends_open += 1
- argument = argument[:-1].strip()
- if argument:
- stack[-1] += addnodes.desc_parameter(
- '', '', addnodes.desc_sig_name(argument, argument))
- while ends_open:
- stack.append(addnodes.desc_optional())
- stack[-2] += stack[-1]
- ends_open -= 1
- while ends_close:
- stack.pop()
- ends_close -= 1
- if len(stack) != 1:
- raise IndexError
- except IndexError:
- # if there are too few or too many elements on the stack, just give up
- # and treat the whole argument list as one argument, discarding the
- # already partially populated paramlist node
- paramlist = addnodes.desc_parameterlist()
- paramlist += addnodes.desc_parameter(arglist, arglist)
- signode += paramlist
- else:
- signode += paramlist
-
-
-# This override allows our inline type specifiers to behave like :class: link
-# when it comes to handling "." and "~" prefixes.
-class PyXrefMixin:
- def make_xref(
- self,
- rolename: str,
- domain: str,
- target: str,
- innernode: type[TextlikeNode] = nodes.emphasis,
- contnode: Node | None = None,
- env: BuildEnvironment | None = None,
- inliner: Inliner | None = None,
- location: Node | None = None,
- ) -> Node:
- # we use inliner=None to make sure we get the old behaviour with a single
- # pending_xref node
- result = super().make_xref(rolename, domain, target, # type: ignore[misc]
- innernode, contnode,
- env, inliner=None, location=None)
- if isinstance(result, pending_xref):
- assert env is not None
- result['refspecific'] = True
- result['py:module'] = env.ref_context.get('py:module')
- result['py:class'] = env.ref_context.get('py:class')
-
- reftype, reftarget, reftitle, _ = parse_reftarget(target)
- if reftarget != reftitle:
- result['reftype'] = reftype
- result['reftarget'] = reftarget
-
- result.clear()
- result += innernode(reftitle, reftitle)
- elif env.config.python_use_unqualified_type_names:
- children = result.children
- result.clear()
-
- shortname = target.split('.')[-1]
- textnode = innernode('', shortname)
- contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
- pending_xref_condition('', '', *children, condition='*')]
- result.extend(contnodes)
-
- return result
-
- def make_xrefs(
- self,
- rolename: str,
- domain: str,
- target: str,
- innernode: type[TextlikeNode] = nodes.emphasis,
- contnode: Node | None = None,
- env: BuildEnvironment | None = None,
- inliner: Inliner | None = None,
- location: Node | None = None,
- ) -> list[Node]:
- delims = r'(\s*[\[\]\(\),](?:\s*o[rf]\s)?\s*|\s+o[rf]\s+|\s*\|\s*|\.\.\.)'
- delims_re = re.compile(delims)
- sub_targets = re.split(delims, target)
-
- split_contnode = bool(contnode and contnode.astext() == target)
-
- in_literal = False
- results = []
- for sub_target in filter(None, sub_targets):
- if split_contnode:
- contnode = nodes.Text(sub_target)
-
- if in_literal or delims_re.match(sub_target):
- results.append(contnode or innernode(sub_target, sub_target))
- else:
- results.append(self.make_xref(rolename, domain, sub_target,
- innernode, contnode, env, inliner, location))
-
- if sub_target in ('Literal', 'typing.Literal', '~typing.Literal'):
- in_literal = True
-
- return results
-
-
-class PyField(PyXrefMixin, Field):
- pass
-
-
-class PyGroupedField(PyXrefMixin, GroupedField):
- pass
-
-
-class PyTypedField(PyXrefMixin, TypedField):
- pass
-
-
-class PyObject(ObjectDescription[tuple[str, str]]):
- """
- Description of a general Python object.
-
- :cvar allow_nesting: Class is an object that allows for nested namespaces
- :vartype allow_nesting: bool
- """
- option_spec: OptionSpec = {
- 'no-index': directives.flag,
- 'no-index-entry': directives.flag,
- 'no-contents-entry': directives.flag,
- 'no-typesetting': directives.flag,
- 'noindex': directives.flag,
- 'noindexentry': directives.flag,
- 'nocontentsentry': directives.flag,
- 'single-line-parameter-list': directives.flag,
- 'single-line-type-parameter-list': directives.flag,
- 'module': directives.unchanged,
- 'canonical': directives.unchanged,
- 'annotation': directives.unchanged,
- }
-
- doc_field_types = [
- PyTypedField('parameter', label=_('Parameters'),
- names=('param', 'parameter', 'arg', 'argument',
- 'keyword', 'kwarg', 'kwparam'),
- typerolename='class', typenames=('paramtype', 'type'),
- can_collapse=True),
- PyTypedField('variable', label=_('Variables'),
- names=('var', 'ivar', 'cvar'),
- typerolename='class', typenames=('vartype',),
- can_collapse=True),
- PyGroupedField('exceptions', label=_('Raises'), rolename='exc',
- names=('raises', 'raise', 'exception', 'except'),
- can_collapse=True),
- Field('returnvalue', label=_('Returns'), has_arg=False,
- names=('returns', 'return')),
- PyField('returntype', label=_('Return type'), has_arg=False,
- names=('rtype',), bodyrolename='class'),
- ]
-
- allow_nesting = False
-
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
- """May return a prefix to put before the object name in the
- signature.
- """
- return []
-
- def needs_arglist(self) -> bool:
- """May return true if an empty argument list is to be generated even if
- the document contains none.
- """
- return False
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- """Transform a Python signature into RST nodes.
-
- Return (fully qualified name of the thing, classname if any).
-
- If inside a class, the current class name is handled intelligently:
- * it is stripped from the displayed name if present
- * it is added to the full name (return value) if not present
- """
- m = py_sig_re.match(sig)
- if m is None:
- raise ValueError
- prefix, name, tp_list, arglist, retann = m.groups()
-
- # determine module and class name (if applicable), as well as full name
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
- classname = self.env.ref_context.get('py:class')
- if classname:
- add_module = False
- if prefix and (prefix == classname or
- prefix.startswith(classname + ".")):
- fullname = prefix + name
- # class name is given again in the signature
- prefix = prefix[len(classname):].lstrip('.')
- elif prefix:
- # class name is given in the signature, but different
- # (shouldn't happen)
- fullname = classname + '.' + prefix + name
- else:
- # class name is not given in the signature
- fullname = classname + '.' + name
- else:
- add_module = True
- if prefix:
- classname = prefix.rstrip('.')
- fullname = prefix + name
- else:
- classname = ''
- fullname = name
-
- signode['module'] = modname
- signode['class'] = classname
- signode['fullname'] = fullname
-
- max_len = (self.env.config.python_maximum_signature_line_length
- or self.env.config.maximum_signature_line_length
- or 0)
-
- # determine if the function arguments (without its type parameters)
- # should be formatted on a multiline or not by removing the width of
- # the type parameters list (if any)
- sig_len = len(sig)
- tp_list_span = m.span(3)
- multi_line_parameter_list = (
- 'single-line-parameter-list' not in self.options
- and (sig_len - (tp_list_span[1] - tp_list_span[0])) > max_len > 0
- )
-
- # determine whether the type parameter list must be wrapped or not
- arglist_span = m.span(4)
- multi_line_type_parameter_list = (
- 'single-line-type-parameter-list' not in self.options
- and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0
- )
-
- sig_prefix = self.get_signature_prefix(sig)
- if sig_prefix:
- if type(sig_prefix) is str:
- msg = ("Python directive method get_signature_prefix()"
- " must return a list of nodes."
- f" Return value was '{sig_prefix}'.")
- raise TypeError(msg)
- signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix)
-
- if prefix:
- signode += addnodes.desc_addname(prefix, prefix)
- elif modname and add_module and self.env.config.add_module_names:
- nodetext = modname + '.'
- signode += addnodes.desc_addname(nodetext, nodetext)
-
- signode += addnodes.desc_name(name, name)
-
- if tp_list:
- try:
- signode += _parse_type_list(tp_list, self.env, multi_line_type_parameter_list)
- except Exception as exc:
- logger.warning("could not parse tp_list (%r): %s", tp_list, exc,
- location=signode)
-
- if arglist:
- try:
- signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
- except SyntaxError:
- # fallback to parse arglist original parser
- # (this may happen if the argument list is incorrectly used
- # as a list of bases when documenting a class)
- # it supports to represent optional arguments (ex. "func(foo [, bar])")
- _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
- except (NotImplementedError, ValueError) as exc:
- # duplicated parameter names raise ValueError and not a SyntaxError
- logger.warning("could not parse arglist (%r): %s", arglist, exc,
- location=signode)
- _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
- else:
- if self.needs_arglist():
- # for callables, add an empty parameter list
- signode += addnodes.desc_parameterlist()
-
- if retann:
- children = _parse_annotation(retann, self.env)
- signode += addnodes.desc_returns(retann, '', *children)
-
- anno = self.options.get('annotation')
- if anno:
- signode += addnodes.desc_annotation(' ' + anno, '',
- addnodes.desc_sig_space(),
- nodes.Text(anno))
-
- return fullname, prefix
-
- def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
- if 'fullname' not in sig_node:
- return ()
- modname = sig_node.get('module')
- fullname = sig_node['fullname']
-
- if modname:
- return (modname, *fullname.split('.'))
- else:
- return tuple(fullname.split('.'))
-
- def get_index_text(self, modname: str, name: tuple[str, str]) -> str:
- """Return the text for the index entry of the object."""
- msg = 'must be implemented in subclasses'
- raise NotImplementedError(msg)
-
- def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
- signode: desc_signature) -> None:
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
- fullname = (modname + '.' if modname else '') + name_cls[0]
- node_id = make_id(self.env, self.state.document, '', fullname)
- signode['ids'].append(node_id)
- self.state.document.note_explicit_target(signode)
-
- domain = cast(PythonDomain, self.env.get_domain('py'))
- domain.note_object(fullname, self.objtype, node_id, location=signode)
-
- canonical_name = self.options.get('canonical')
- if canonical_name:
- domain.note_object(canonical_name, self.objtype, node_id, aliased=True,
- location=signode)
-
- if 'no-index-entry' not in self.options:
- indextext = self.get_index_text(modname, name_cls)
- if indextext:
- self.indexnode['entries'].append(('single', indextext, node_id, '', None))
-
- def before_content(self) -> None:
- """Handle object nesting before content
-
- :py:class:`PyObject` represents Python language constructs. For
- constructs that are nestable, such as a Python classes, this method will
- build up a stack of the nesting hierarchy so that it can be later
- de-nested correctly, in :py:meth:`after_content`.
-
- For constructs that aren't nestable, the stack is bypassed, and instead
- only the most recent object is tracked. This object prefix name will be
- removed with :py:meth:`after_content`.
- """
- prefix = None
- if self.names:
- # fullname and name_prefix come from the `handle_signature` method.
- # fullname represents the full object name that is constructed using
- # object nesting and explicit prefixes. `name_prefix` is the
- # explicit prefix given in a signature
- (fullname, name_prefix) = self.names[-1]
- if self.allow_nesting:
- prefix = fullname
- elif name_prefix:
- prefix = name_prefix.strip('.')
- if prefix:
- self.env.ref_context['py:class'] = prefix
- if self.allow_nesting:
- classes = self.env.ref_context.setdefault('py:classes', [])
- classes.append(prefix)
- if 'module' in self.options:
- modules = self.env.ref_context.setdefault('py:modules', [])
- modules.append(self.env.ref_context.get('py:module'))
- self.env.ref_context['py:module'] = self.options['module']
-
- def after_content(self) -> None:
- """Handle object de-nesting after content
-
- If this class is a nestable object, removing the last nested class prefix
- ends further nesting in the object.
-
- If this class is not a nestable object, the list of classes should not
- be altered as we didn't affect the nesting levels in
- :py:meth:`before_content`.
- """
- classes = self.env.ref_context.setdefault('py:classes', [])
- if self.allow_nesting:
- with contextlib.suppress(IndexError):
- classes.pop()
-
- self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0
- else None)
- if 'module' in self.options:
- modules = self.env.ref_context.setdefault('py:modules', [])
- if modules:
- self.env.ref_context['py:module'] = modules.pop()
- else:
- self.env.ref_context.pop('py:module')
-
- 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 sig_node.get('fullname', 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 PyFunction(PyObject):
- """Description of a function."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
- option_spec.update({
- 'async': directives.flag,
- })
-
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
- if 'async' in self.options:
- return [addnodes.desc_sig_keyword('', 'async'),
- addnodes.desc_sig_space()]
- else:
- return []
-
- def needs_arglist(self) -> bool:
- return True
-
- def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
- signode: desc_signature) -> None:
- super().add_target_and_index(name_cls, sig, signode)
- if 'no-index-entry' not in self.options:
- modname = self.options.get('module', self.env.ref_context.get('py:module'))
- node_id = signode['ids'][0]
-
- name, cls = name_cls
- if modname:
- text = _('%s() (in module %s)') % (name, modname)
- self.indexnode['entries'].append(('single', text, node_id, '', None))
- else:
- text = f'built-in function; {name}()'
- self.indexnode['entries'].append(('pair', text, node_id, '', None))
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- # add index in own add_target_and_index() instead.
- return ''
-
-
-class PyDecoratorFunction(PyFunction):
- """Description of a decorator."""
-
- def run(self) -> list[Node]:
- # a decorator function is a function after all
- self.name = 'py:function'
- return super().run()
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- ret = super().handle_signature(sig, signode)
- signode.insert(0, addnodes.desc_addname('@', '@'))
- return ret
-
- def needs_arglist(self) -> bool:
- return False
-
-
-class PyVariable(PyObject):
- """Description of a variable."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
- option_spec.update({
- 'type': directives.unchanged,
- 'value': directives.unchanged,
- })
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- fullname, prefix = super().handle_signature(sig, signode)
-
- typ = self.options.get('type')
- if typ:
- annotations = _parse_annotation(typ, self.env)
- signode += addnodes.desc_annotation(typ, '',
- addnodes.desc_sig_punctuation('', ':'),
- addnodes.desc_sig_space(), *annotations)
-
- value = self.options.get('value')
- if value:
- signode += addnodes.desc_annotation(value, '',
- addnodes.desc_sig_space(),
- addnodes.desc_sig_punctuation('', '='),
- addnodes.desc_sig_space(),
- nodes.Text(value))
-
- return fullname, prefix
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- name, cls = name_cls
- if modname:
- return _('%s (in module %s)') % (name, modname)
- else:
- return _('%s (built-in variable)') % name
-
-
-class PyClasslike(PyObject):
- """
- Description of a class-like object (classes, interfaces, exceptions).
- """
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
- option_spec.update({
- 'final': directives.flag,
- })
-
- allow_nesting = True
-
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
- if 'final' in self.options:
- return [nodes.Text('final'), addnodes.desc_sig_space(),
- nodes.Text(self.objtype), addnodes.desc_sig_space()]
- else:
- return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- if self.objtype == 'class':
- if not modname:
- return _('%s (built-in class)') % name_cls[0]
- return _('%s (class in %s)') % (name_cls[0], modname)
- elif self.objtype == 'exception':
- return name_cls[0]
- else:
- return ''
-
-
-class PyMethod(PyObject):
- """Description of a method."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
- option_spec.update({
- 'abstractmethod': directives.flag,
- 'async': directives.flag,
- 'classmethod': directives.flag,
- 'final': directives.flag,
- 'staticmethod': directives.flag,
- })
-
- def needs_arglist(self) -> bool:
- return True
-
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
- prefix: list[nodes.Node] = []
- if 'final' in self.options:
- prefix.append(nodes.Text('final'))
- prefix.append(addnodes.desc_sig_space())
- if 'abstractmethod' in self.options:
- prefix.append(nodes.Text('abstract'))
- prefix.append(addnodes.desc_sig_space())
- if 'async' in self.options:
- prefix.append(nodes.Text('async'))
- prefix.append(addnodes.desc_sig_space())
- if 'classmethod' in self.options:
- prefix.append(nodes.Text('classmethod'))
- prefix.append(addnodes.desc_sig_space())
- if 'staticmethod' in self.options:
- prefix.append(nodes.Text('static'))
- prefix.append(addnodes.desc_sig_space())
- return prefix
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- name, cls = name_cls
- try:
- clsname, methname = name.rsplit('.', 1)
- if modname and self.env.config.add_module_names:
- clsname = '.'.join([modname, clsname])
- except ValueError:
- if modname:
- return _('%s() (in module %s)') % (name, modname)
- else:
- return '%s()' % name
-
- if 'classmethod' in self.options:
- return _('%s() (%s class method)') % (methname, clsname)
- elif 'staticmethod' in self.options:
- return _('%s() (%s static method)') % (methname, clsname)
- else:
- return _('%s() (%s method)') % (methname, clsname)
-
-
-class PyClassMethod(PyMethod):
- """Description of a classmethod."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
-
- def run(self) -> list[Node]:
- self.name = 'py:method'
- self.options['classmethod'] = True
-
- return super().run()
-
-
-class PyStaticMethod(PyMethod):
- """Description of a staticmethod."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
-
- def run(self) -> list[Node]:
- self.name = 'py:method'
- self.options['staticmethod'] = True
-
- return super().run()
-
-
-class PyDecoratorMethod(PyMethod):
- """Description of a decoratormethod."""
-
- def run(self) -> list[Node]:
- self.name = 'py:method'
- return super().run()
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- ret = super().handle_signature(sig, signode)
- signode.insert(0, addnodes.desc_addname('@', '@'))
- return ret
-
- def needs_arglist(self) -> bool:
- return False
-
-
-class PyAttribute(PyObject):
- """Description of an attribute."""
-
- option_spec: OptionSpec = PyObject.option_spec.copy()
- option_spec.update({
- 'type': directives.unchanged,
- 'value': directives.unchanged,
- })
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- fullname, prefix = super().handle_signature(sig, signode)
-
- typ = self.options.get('type')
- if typ:
- annotations = _parse_annotation(typ, self.env)
- signode += addnodes.desc_annotation(typ, '',
- addnodes.desc_sig_punctuation('', ':'),
- addnodes.desc_sig_space(),
- *annotations)
-
- value = self.options.get('value')
- if value:
- signode += addnodes.desc_annotation(value, '',
- addnodes.desc_sig_space(),
- addnodes.desc_sig_punctuation('', '='),
- addnodes.desc_sig_space(),
- nodes.Text(value))
-
- return fullname, prefix
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- name, cls = name_cls
- try:
- clsname, attrname = name.rsplit('.', 1)
- if modname and self.env.config.add_module_names:
- clsname = '.'.join([modname, clsname])
- except ValueError:
- if modname:
- return _('%s (in module %s)') % (name, modname)
- else:
- return name
-
- return _('%s (%s attribute)') % (attrname, clsname)
-
-
-class PyProperty(PyObject):
- """Description of an attribute."""
-
- option_spec = PyObject.option_spec.copy()
- option_spec.update({
- 'abstractmethod': directives.flag,
- 'classmethod': directives.flag,
- 'type': directives.unchanged,
- })
-
- def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
- fullname, prefix = super().handle_signature(sig, signode)
-
- typ = self.options.get('type')
- if typ:
- annotations = _parse_annotation(typ, self.env)
- signode += addnodes.desc_annotation(typ, '',
- addnodes.desc_sig_punctuation('', ':'),
- addnodes.desc_sig_space(),
- *annotations)
-
- return fullname, prefix
-
- def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
- prefix: list[nodes.Node] = []
- if 'abstractmethod' in self.options:
- prefix.append(nodes.Text('abstract'))
- prefix.append(addnodes.desc_sig_space())
- if 'classmethod' in self.options:
- prefix.append(nodes.Text('class'))
- prefix.append(addnodes.desc_sig_space())
-
- prefix.append(nodes.Text('property'))
- prefix.append(addnodes.desc_sig_space())
- return prefix
-
- def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
- name, cls = name_cls
- try:
- clsname, attrname = name.rsplit('.', 1)
- if modname and self.env.config.add_module_names:
- clsname = '.'.join([modname, clsname])
- except ValueError:
- if modname:
- return _('%s (in module %s)') % (name, modname)
- else:
- return name
-
- return _('%s (%s property)') % (attrname, clsname)
-
-
-class PyModule(SphinxDirective):
- """
- Directive to mark description of a new module.
- """
-
- has_content = True
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = False
- option_spec: OptionSpec = {
- 'platform': lambda x: x,
- 'synopsis': lambda x: x,
- 'no-index': directives.flag,
- 'no-contents-entry': directives.flag,
- 'no-typesetting': directives.flag,
- 'noindex': directives.flag,
- 'nocontentsentry': directives.flag,
- 'deprecated': directives.flag,
- }
-
- def run(self) -> list[Node]:
- domain = cast(PythonDomain, self.env.get_domain('py'))
-
- modname = self.arguments[0].strip()
- no_index = 'no-index' in self.options or 'noindex' in self.options
- self.env.ref_context['py:module'] = modname
-
- content_node: Element = nodes.section()
- # necessary so that the child nodes get the right source/line set
- content_node.document = self.state.document
- nested_parse_with_titles(self.state, self.content, content_node, self.content_offset)
-
- ret: list[Node] = []
- if not no_index:
- # note module to the domain
- node_id = make_id(self.env, self.state.document, 'module', modname)
- target = nodes.target('', '', ids=[node_id], ismod=True)
- self.set_source_info(target)
- self.state.document.note_explicit_target(target)
-
- domain.note_module(modname,
- node_id,
- self.options.get('synopsis', ''),
- self.options.get('platform', ''),
- 'deprecated' in self.options)
- domain.note_object(modname, 'module', node_id, location=target)
-
- # the platform and synopsis aren't printed; in fact, they are only
- # used in the modindex currently
- indextext = f'module; {modname}'
- inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
- # The node order is: index node first, then target node.
- ret.append(inode)
- ret.append(target)
- ret.extend(content_node.children)
- return ret
-
-
-class PyCurrentModule(SphinxDirective):
- """
- This directive is just to tell Sphinx that we're documenting
- stuff in module foo, but links to module foo won't lead here.
- """
-
- has_content = False
- required_arguments = 1
- optional_arguments = 0
- final_argument_whitespace = False
- option_spec: OptionSpec = {}
-
- def run(self) -> list[Node]:
- modname = self.arguments[0].strip()
- if modname == 'None':
- self.env.ref_context.pop('py:module', None)
- else:
- self.env.ref_context['py:module'] = modname
- return []
-
-
-class PyXRefRole(XRefRole):
- def process_link(self, env: BuildEnvironment, refnode: Element,
- has_explicit_title: bool, title: str, target: str) -> tuple[str, str]:
- refnode['py:module'] = env.ref_context.get('py:module')
- refnode['py:class'] = env.ref_context.get('py:class')
- if not has_explicit_title:
- title = title.lstrip('.') # only has a meaning for the target
- 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[0:1] == '~':
- title = title[1:]
- dot = title.rfind('.')
- if dot != -1:
- title = title[dot + 1:]
- # if the first character is a dot, search more specific namespaces first
- # else search builtins first
- if target[0:1] == '.':
- target = target[1:]
- refnode['refspecific'] = True
- return title, target
-
-
-def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None:
- """Filter ``:meta:`` field from its docstring."""
- if domain != 'py':
- return
-
- for node in content:
- if isinstance(node, nodes.field_list):
- fields = cast(list[nodes.field], node)
- # removing list items while iterating the list needs reversed()
- for field in reversed(fields):
- field_name = cast(nodes.field_body, field[0]).astext().strip()
- if field_name == 'meta' or field_name.startswith('meta '):
- node.remove(field)
-
-
-class PythonModuleIndex(Index):
- """
- Index subclass to provide the Python module index.
- """
-
- name = 'modindex'
- localname = _('Python Module Index')
- shortname = _('modules')
-
- def generate(self, docnames: Iterable[str] | None = None,
- ) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
- content: dict[str, list[IndexEntry]] = {}
- # list of prefixes to ignore
- ignores: list[str] = self.domain.env.config['modindex_common_prefix']
- ignores = sorted(ignores, key=len, reverse=True)
- # list of all modules, sorted by module name
- modules = sorted(self.domain.data['modules'].items(),
- key=lambda x: x[0].lower())
- # sort out collapsible modules
- prev_modname = ''
- num_toplevels = 0
- for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
- if docnames and docname not in docnames:
- continue
-
- for ignore in ignores:
- if modname.startswith(ignore):
- modname = modname[len(ignore):]
- stripped = ignore
- break
- else:
- stripped = ''
-
- # we stripped the whole module name?
- if not modname:
- modname, stripped = stripped, ''
-
- entries = content.setdefault(modname[0].lower(), [])
-
- package = modname.split('.')[0]
- if package != modname:
- # it's a submodule
- if prev_modname == package:
- # first submodule - make parent a group head
- if entries:
- last = entries[-1]
- entries[-1] = IndexEntry(last[0], 1, last[2], last[3],
- last[4], last[5], last[6])
- elif not prev_modname.startswith(package):
- # submodule without parent in list, add dummy entry
- entries.append(IndexEntry(stripped + package, 1, '', '', '', '', ''))
- subtype = 2
- else:
- num_toplevels += 1
- subtype = 0
-
- qualifier = _('Deprecated') if deprecated else ''
- entries.append(IndexEntry(stripped + modname, subtype, docname,
- node_id, platforms, qualifier, synopsis))
- prev_modname = modname
-
- # apply heuristics when to collapse modindex at page load:
- # only collapse if number of toplevel modules is larger than
- # number of submodules
- collapse = len(modules) - num_toplevels < num_toplevels
-
- # sort by first letter
- sorted_content = sorted(content.items())
-
- return sorted_content, collapse
-
-
-class PythonDomain(Domain):
- """Python language domain."""
- name = 'py'
- label = 'Python'
- object_types: dict[str, ObjType] = {
- 'function': ObjType(_('function'), 'func', 'obj'),
- 'data': ObjType(_('data'), 'data', 'obj'),
- 'class': ObjType(_('class'), 'class', 'exc', 'obj'),
- 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'),
- 'method': ObjType(_('method'), 'meth', 'obj'),
- 'classmethod': ObjType(_('class method'), 'meth', 'obj'),
- 'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
- 'attribute': ObjType(_('attribute'), 'attr', 'obj'),
- 'property': ObjType(_('property'), 'attr', '_prop', 'obj'),
- 'module': ObjType(_('module'), 'mod', 'obj'),
- }
-
- directives = {
- 'function': PyFunction,
- 'data': PyVariable,
- 'class': PyClasslike,
- 'exception': PyClasslike,
- 'method': PyMethod,
- 'classmethod': PyClassMethod,
- 'staticmethod': PyStaticMethod,
- 'attribute': PyAttribute,
- 'property': PyProperty,
- 'module': PyModule,
- 'currentmodule': PyCurrentModule,
- 'decorator': PyDecoratorFunction,
- 'decoratormethod': PyDecoratorMethod,
- }
- roles = {
- 'data': PyXRefRole(),
- 'exc': PyXRefRole(),
- 'func': PyXRefRole(fix_parens=True),
- 'class': PyXRefRole(),
- 'const': PyXRefRole(),
- 'attr': PyXRefRole(),
- 'meth': PyXRefRole(fix_parens=True),
- 'mod': PyXRefRole(),
- 'obj': PyXRefRole(),
- }
- initial_data: dict[str, dict[str, tuple[Any]]] = {
- 'objects': {}, # fullname -> docname, objtype
- 'modules': {}, # modname -> docname, synopsis, platform, deprecated
- }
- indices = [
- PythonModuleIndex,
- ]
-
- @property
- def objects(self) -> dict[str, ObjectEntry]:
- return self.data.setdefault('objects', {}) # fullname -> ObjectEntry
-
- def note_object(self, name: str, objtype: str, node_id: str,
- aliased: bool = False, location: Any = None) -> None:
- """Note a python object for cross reference.
-
- .. versionadded:: 2.1
- """
- if name in self.objects:
- other = self.objects[name]
- if other.aliased and aliased is False:
- # The original definition found. Override it!
- pass
- elif other.aliased is False and aliased:
- # The original definition is already registered.
- return
- else:
- # duplicated
- logger.warning(__('duplicate object description of %s, '
- 'other instance in %s, use :no-index: for one of them'),
- name, other.docname, location=location)
- self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased)
-
- @property
- def modules(self) -> dict[str, ModuleEntry]:
- return self.data.setdefault('modules', {}) # modname -> ModuleEntry
-
- def note_module(self, name: str, node_id: str, synopsis: str,
- platform: str, deprecated: bool) -> None:
- """Note a python module for cross reference.
-
- .. versionadded:: 2.1
- """
- self.modules[name] = ModuleEntry(self.env.docname, node_id,
- synopsis, platform, deprecated)
-
- def clear_doc(self, docname: str) -> None:
- for fullname, obj in list(self.objects.items()):
- if obj.docname == docname:
- del self.objects[fullname]
- for modname, mod in list(self.modules.items()):
- if mod.docname == docname:
- del self.modules[modname]
-
- def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
- # XXX check duplicates?
- for fullname, obj in otherdata['objects'].items():
- if obj.docname in docnames:
- self.objects[fullname] = obj
- for modname, mod in otherdata['modules'].items():
- if mod.docname in docnames:
- self.modules[modname] = mod
-
- def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
- name: str, type: str | None, searchmode: int = 0,
- ) -> list[tuple[str, ObjectEntry]]:
- """Find a Python object for "name", perhaps using the given module
- and/or classname. Returns a list of (name, object entry) tuples.
- """
- # skip parens
- if name[-2:] == '()':
- name = name[:-2]
-
- if not name:
- return []
-
- matches: list[tuple[str, ObjectEntry]] = []
-
- newname = None
- if searchmode == 1:
- if type is None:
- objtypes: list[str] | None = list(self.object_types)
- else:
- objtypes = self.objtypes_for_role(type)
- if objtypes is not None:
- if modname and classname:
- fullname = modname + '.' + classname + '.' + name
- if fullname in self.objects and self.objects[fullname].objtype in objtypes:
- newname = fullname
- if not newname:
- if modname and modname + '.' + name in self.objects and \
- self.objects[modname + '.' + name].objtype in objtypes:
- newname = modname + '.' + name
- elif name in self.objects and self.objects[name].objtype in objtypes:
- newname = name
- else:
- # "fuzzy" searching mode
- searchname = '.' + name
- matches = [(oname, self.objects[oname]) for oname in self.objects
- if oname.endswith(searchname) and
- self.objects[oname].objtype in objtypes]
- else:
- # NOTE: searching for exact match, object type is not considered
- if name in self.objects:
- newname = name
- elif type == 'mod':
- # only exact matches allowed for modules
- return []
- elif classname and classname + '.' + name in self.objects:
- newname = classname + '.' + name
- elif modname and modname + '.' + name in self.objects:
- newname = modname + '.' + name
- elif modname and classname and \
- modname + '.' + classname + '.' + name in self.objects:
- newname = modname + '.' + classname + '.' + name
- if newname is not None:
- matches.append((newname, self.objects[newname]))
- return matches
-
- def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
- type: str, target: str, node: pending_xref, contnode: Element,
- ) -> Element | None:
- modname = node.get('py:module')
- clsname = node.get('py:class')
- searchmode = 1 if node.hasattr('refspecific') else 0
- matches = self.find_obj(env, modname, clsname, target,
- type, searchmode)
-
- if not matches and type == 'attr':
- # fallback to meth (for property; Sphinx 2.4.x)
- # this ensures that `:attr:` role continues to refer to the old property entry
- # that defined by ``method`` directive in old reST files.
- matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode)
- if not matches and type == 'meth':
- # fallback to attr (for property)
- # this ensures that `:meth:` in the old reST files can refer to the property
- # entry that defined by ``property`` directive.
- #
- # Note: _prop is a secret role only for internal look-up.
- matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode)
-
- if not matches:
- return None
- elif len(matches) > 1:
- canonicals = [m for m in matches if not m[1].aliased]
- if len(canonicals) == 1:
- matches = canonicals
- else:
- logger.warning(__('more than one target found for cross-reference %r: %s'),
- target, ', '.join(match[0] for match in matches),
- type='ref', subtype='python', location=node)
- name, obj = matches[0]
-
- if obj[2] == 'module':
- return self._make_module_refnode(builder, fromdocname, name, contnode)
- else:
- # determine the content of the reference by conditions
- content = find_pending_xref_condition(node, 'resolved')
- if content:
- children = content.children
- else:
- # if not found, use contnode
- children = [contnode]
-
- return make_refnode(builder, fromdocname, obj[0], obj[1], children, name)
-
- def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
- target: str, node: pending_xref, contnode: Element,
- ) -> list[tuple[str, Element]]:
- modname = node.get('py:module')
- clsname = node.get('py:class')
- results: list[tuple[str, Element]] = []
-
- # always search in "refspecific" mode with the :any: role
- matches = self.find_obj(env, modname, clsname, target, None, 1)
- multiple_matches = len(matches) > 1
-
- for name, obj in matches:
-
- if multiple_matches and obj.aliased:
- # Skip duplicated matches
- continue
-
- if obj[2] == 'module':
- results.append(('py:mod',
- self._make_module_refnode(builder, fromdocname,
- name, contnode)))
- else:
- # determine the content of the reference by conditions
- content = find_pending_xref_condition(node, 'resolved')
- if content:
- children = content.children
- else:
- # if not found, use contnode
- children = [contnode]
-
- role = 'py:' + self.role_for_objtype(obj[2]) # type: ignore[operator]
- results.append((role, make_refnode(builder, fromdocname, obj[0], obj[1],
- children, name)))
- return results
-
- def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
- contnode: Node) -> Element:
- # get additional info for modules
- module = self.modules[name]
- title = name
- if module.synopsis:
- title += ': ' + module.synopsis
- if module.deprecated:
- title += _(' (deprecated)')
- if module.platform:
- title += ' (' + module.platform + ')'
- return make_refnode(builder, fromdocname, module.docname, module.node_id,
- contnode, title)
-
- def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
- for modname, mod in self.modules.items():
- yield (modname, modname, 'module', mod.docname, mod.node_id, 0)
- for refname, obj in self.objects.items():
- if obj.objtype != 'module': # modules are already handled
- if obj.aliased:
- # aliased names are not full-text searchable.
- yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1)
- else:
- yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
-
- def get_full_qualified_name(self, node: Element) -> str | None:
- modname = node.get('py:module')
- clsname = node.get('py:class')
- target = node.get('reftarget')
- if target is None:
- return None
- else:
- return '.'.join(filter(None, [modname, clsname, target]))
-
-
-def builtin_resolver(app: Sphinx, env: BuildEnvironment,
- node: pending_xref, contnode: Element) -> Element | None:
- """Do not emit nitpicky warnings for built-in types."""
- def istyping(s: str) -> bool:
- if s.startswith('typing.'):
- s = s.split('.', 1)[1]
-
- return s in typing.__all__
-
- if node.get('refdomain') != 'py':
- return None
- elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None':
- return contnode
- elif node.get('reftype') in ('class', 'obj', 'exc'):
- reftarget = node.get('reftarget')
- if inspect.isclass(getattr(builtins, reftarget, None)):
- # built-in class
- return contnode
- if istyping(reftarget):
- # typing class
- return contnode
-
- return None
-
-
-def setup(app: Sphinx) -> dict[str, Any]:
- app.setup_extension('sphinx.directives')
-
- app.add_domain(PythonDomain)
- app.add_config_value('python_use_unqualified_type_names', False, 'env')
- app.add_config_value('python_maximum_signature_line_length', None, 'env',
- types={int, None})
- app.add_config_value('python_display_short_literal_types', False, 'env')
- app.connect('object-description-transform', filter_meta_fields)
- app.connect('missing-reference', builtin_resolver, priority=900)
-
- return {
- 'version': 'builtin',
- 'env_version': 4,
- 'parallel_read_safe': True,
- 'parallel_write_safe': True,
- }
diff --git a/sphinx/domains/python/__init__.py b/sphinx/domains/python/__init__.py
new file mode 100644
index 0000000..ca3eec0
--- /dev/null
+++ b/sphinx/domains/python/__init__.py
@@ -0,0 +1,904 @@
+"""The Python domain."""
+
+from __future__ import annotations
+
+import builtins
+import inspect
+import typing
+from typing import TYPE_CHECKING, Any, ClassVar, NamedTuple, cast
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+from sphinx import addnodes
+from sphinx.domains import Domain, Index, IndexEntry, ObjType
+from sphinx.domains.python._annotations import _parse_annotation
+from sphinx.domains.python._object import PyObject
+from sphinx.locale import _, __
+from sphinx.roles import XRefRole
+from sphinx.util import logging
+from sphinx.util.docutils import SphinxDirective
+from sphinx.util.nodes import (
+ find_pending_xref_condition,
+ make_id,
+ make_refnode,
+ nested_parse_with_titles,
+)
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable, Iterator
+
+ from docutils.nodes import Element, Node
+
+ 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 ExtensionMetadata, OptionSpec
+
+# re-export objects for backwards compatibility
+# xref https://github.com/sphinx-doc/sphinx/issues/12295
+from sphinx.domains.python._annotations import ( # NoQA: F401
+ _parse_arglist, # for sphinx-immaterial
+ type_to_xref,
+)
+from sphinx.domains.python._object import ( # NoQA: F401
+ PyField,
+ PyGroupedField,
+ PyTypedField,
+ PyXrefMixin,
+ py_sig_re,
+)
+
+logger = logging.getLogger(__name__)
+
+pairindextypes = {
+ 'module': 'module',
+ 'keyword': 'keyword',
+ 'operator': 'operator',
+ 'object': 'object',
+ 'exception': 'exception',
+ 'statement': 'statement',
+ 'builtin': 'built-in function',
+}
+
+
+class ObjectEntry(NamedTuple):
+ docname: str
+ node_id: str
+ objtype: str
+ aliased: bool
+
+
+class ModuleEntry(NamedTuple):
+ docname: str
+ node_id: str
+ synopsis: str
+ platform: str
+ deprecated: bool
+
+
+class PyFunction(PyObject):
+ """Description of a function."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy() # noqa: F821
+ option_spec.update({
+ 'async': directives.flag,
+ })
+
+ def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
+ if 'async' in self.options:
+ return [addnodes.desc_sig_keyword('', 'async'),
+ addnodes.desc_sig_space()]
+ else:
+ return []
+
+ def needs_arglist(self) -> bool:
+ return True
+
+ def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
+ signode: desc_signature) -> None:
+ super().add_target_and_index(name_cls, sig, signode)
+ if 'no-index-entry' not in self.options:
+ modname = self.options.get('module', self.env.ref_context.get('py:module'))
+ node_id = signode['ids'][0]
+
+ name, cls = name_cls
+ if modname:
+ text = _('%s() (in module %s)') % (name, modname)
+ self.indexnode['entries'].append(('single', text, node_id, '', None))
+ else:
+ text = f'built-in function; {name}()'
+ self.indexnode['entries'].append(('pair', text, node_id, '', None))
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ # add index in own add_target_and_index() instead.
+ return ''
+
+
+class PyDecoratorFunction(PyFunction):
+ """Description of a decorator."""
+
+ def run(self) -> list[Node]:
+ # a decorator function is a function after all
+ self.name = 'py:function'
+ return super().run()
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ ret = super().handle_signature(sig, signode)
+ signode.insert(0, addnodes.desc_addname('@', '@'))
+ return ret
+
+ def needs_arglist(self) -> bool:
+ return False
+
+
+class PyVariable(PyObject):
+ """Description of a variable."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+ option_spec.update({
+ 'type': directives.unchanged,
+ 'value': directives.unchanged,
+ })
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ fullname, prefix = super().handle_signature(sig, signode)
+
+ typ = self.options.get('type')
+ if typ:
+ annotations = _parse_annotation(typ, self.env)
+ signode += addnodes.desc_annotation(typ, '',
+ addnodes.desc_sig_punctuation('', ':'),
+ addnodes.desc_sig_space(), *annotations)
+
+ value = self.options.get('value')
+ if value:
+ signode += addnodes.desc_annotation(value, '',
+ addnodes.desc_sig_space(),
+ addnodes.desc_sig_punctuation('', '='),
+ addnodes.desc_sig_space(),
+ nodes.Text(value))
+
+ return fullname, prefix
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ name, cls = name_cls
+ if modname:
+ return _('%s (in module %s)') % (name, modname)
+ else:
+ return _('%s (built-in variable)') % name
+
+
+class PyClasslike(PyObject):
+ """
+ Description of a class-like object (classes, interfaces, exceptions).
+ """
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+ option_spec.update({
+ 'final': directives.flag,
+ })
+
+ allow_nesting = True
+
+ def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
+ if 'final' in self.options:
+ return [nodes.Text('final'), addnodes.desc_sig_space(),
+ nodes.Text(self.objtype), addnodes.desc_sig_space()]
+ else:
+ return [nodes.Text(self.objtype), addnodes.desc_sig_space()]
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ if self.objtype == 'class':
+ if not modname:
+ return _('%s (built-in class)') % name_cls[0]
+ return _('%s (class in %s)') % (name_cls[0], modname)
+ elif self.objtype == 'exception':
+ return name_cls[0]
+ else:
+ return ''
+
+
+class PyMethod(PyObject):
+ """Description of a method."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+ option_spec.update({
+ 'abstractmethod': directives.flag,
+ 'async': directives.flag,
+ 'classmethod': directives.flag,
+ 'final': directives.flag,
+ 'staticmethod': directives.flag,
+ })
+
+ def needs_arglist(self) -> bool:
+ return True
+
+ def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
+ prefix: list[nodes.Node] = []
+ if 'final' in self.options:
+ prefix.append(nodes.Text('final'))
+ prefix.append(addnodes.desc_sig_space())
+ if 'abstractmethod' in self.options:
+ prefix.append(nodes.Text('abstract'))
+ prefix.append(addnodes.desc_sig_space())
+ if 'async' in self.options:
+ prefix.append(nodes.Text('async'))
+ prefix.append(addnodes.desc_sig_space())
+ if 'classmethod' in self.options:
+ prefix.append(nodes.Text('classmethod'))
+ prefix.append(addnodes.desc_sig_space())
+ if 'staticmethod' in self.options:
+ prefix.append(nodes.Text('static'))
+ prefix.append(addnodes.desc_sig_space())
+ return prefix
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ name, cls = name_cls
+ try:
+ clsname, methname = name.rsplit('.', 1)
+ if modname and self.env.config.add_module_names:
+ clsname = f'{modname}.{clsname}'
+ except ValueError:
+ if modname:
+ return _('%s() (in module %s)') % (name, modname)
+ else:
+ return '%s()' % name
+
+ if 'classmethod' in self.options:
+ return _('%s() (%s class method)') % (methname, clsname)
+ elif 'staticmethod' in self.options:
+ return _('%s() (%s static method)') % (methname, clsname)
+ else:
+ return _('%s() (%s method)') % (methname, clsname)
+
+
+class PyClassMethod(PyMethod):
+ """Description of a classmethod."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+
+ def run(self) -> list[Node]:
+ self.name = 'py:method'
+ self.options['classmethod'] = True
+
+ return super().run()
+
+
+class PyStaticMethod(PyMethod):
+ """Description of a staticmethod."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+
+ def run(self) -> list[Node]:
+ self.name = 'py:method'
+ self.options['staticmethod'] = True
+
+ return super().run()
+
+
+class PyDecoratorMethod(PyMethod):
+ """Description of a decoratormethod."""
+
+ def run(self) -> list[Node]:
+ self.name = 'py:method'
+ return super().run()
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ ret = super().handle_signature(sig, signode)
+ signode.insert(0, addnodes.desc_addname('@', '@'))
+ return ret
+
+ def needs_arglist(self) -> bool:
+ return False
+
+
+class PyAttribute(PyObject):
+ """Description of an attribute."""
+
+ option_spec: ClassVar[OptionSpec] = PyObject.option_spec.copy()
+ option_spec.update({
+ 'type': directives.unchanged,
+ 'value': directives.unchanged,
+ })
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ fullname, prefix = super().handle_signature(sig, signode)
+
+ typ = self.options.get('type')
+ if typ:
+ annotations = _parse_annotation(typ, self.env)
+ signode += addnodes.desc_annotation(typ, '',
+ addnodes.desc_sig_punctuation('', ':'),
+ addnodes.desc_sig_space(),
+ *annotations)
+
+ value = self.options.get('value')
+ if value:
+ signode += addnodes.desc_annotation(value, '',
+ addnodes.desc_sig_space(),
+ addnodes.desc_sig_punctuation('', '='),
+ addnodes.desc_sig_space(),
+ nodes.Text(value))
+
+ return fullname, prefix
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ name, cls = name_cls
+ try:
+ clsname, attrname = name.rsplit('.', 1)
+ if modname and self.env.config.add_module_names:
+ clsname = f'{modname}.{clsname}'
+ except ValueError:
+ if modname:
+ return _('%s (in module %s)') % (name, modname)
+ else:
+ return name
+
+ return _('%s (%s attribute)') % (attrname, clsname)
+
+
+class PyProperty(PyObject):
+ """Description of an attribute."""
+
+ option_spec = PyObject.option_spec.copy()
+ option_spec.update({
+ 'abstractmethod': directives.flag,
+ 'classmethod': directives.flag,
+ 'type': directives.unchanged,
+ })
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ fullname, prefix = super().handle_signature(sig, signode)
+
+ typ = self.options.get('type')
+ if typ:
+ annotations = _parse_annotation(typ, self.env)
+ signode += addnodes.desc_annotation(typ, '',
+ addnodes.desc_sig_punctuation('', ':'),
+ addnodes.desc_sig_space(),
+ *annotations)
+
+ return fullname, prefix
+
+ def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
+ prefix: list[nodes.Node] = []
+ if 'abstractmethod' in self.options:
+ prefix.append(nodes.Text('abstract'))
+ prefix.append(addnodes.desc_sig_space())
+ if 'classmethod' in self.options:
+ prefix.append(nodes.Text('class'))
+ prefix.append(addnodes.desc_sig_space())
+
+ prefix.append(nodes.Text('property'))
+ prefix.append(addnodes.desc_sig_space())
+ return prefix
+
+ def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
+ name, cls = name_cls
+ try:
+ clsname, attrname = name.rsplit('.', 1)
+ if modname and self.env.config.add_module_names:
+ clsname = f'{modname}.{clsname}'
+ except ValueError:
+ if modname:
+ return _('%s (in module %s)') % (name, modname)
+ else:
+ return name
+
+ return _('%s (%s property)') % (attrname, clsname)
+
+
+class PyModule(SphinxDirective):
+ """
+ Directive to mark description of a new module.
+ """
+
+ has_content = True
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec: ClassVar[OptionSpec] = {
+ 'platform': lambda x: x,
+ 'synopsis': lambda x: x,
+ 'no-index': directives.flag,
+ 'no-contents-entry': directives.flag,
+ 'no-typesetting': directives.flag,
+ 'noindex': directives.flag,
+ 'nocontentsentry': directives.flag,
+ 'deprecated': directives.flag,
+ }
+
+ def run(self) -> list[Node]:
+ domain = cast(PythonDomain, self.env.get_domain('py'))
+
+ modname = self.arguments[0].strip()
+ no_index = 'no-index' in self.options or 'noindex' in self.options
+ self.env.ref_context['py:module'] = modname
+
+ content_node: Element = nodes.section()
+ # necessary so that the child nodes get the right source/line set
+ content_node.document = self.state.document
+ nested_parse_with_titles(self.state, self.content, content_node, self.content_offset)
+
+ ret: list[Node] = []
+ if not no_index:
+ # note module to the domain
+ node_id = make_id(self.env, self.state.document, 'module', modname)
+ target = nodes.target('', '', ids=[node_id], ismod=True)
+ self.set_source_info(target)
+ self.state.document.note_explicit_target(target)
+
+ domain.note_module(modname,
+ node_id,
+ self.options.get('synopsis', ''),
+ self.options.get('platform', ''),
+ 'deprecated' in self.options)
+ domain.note_object(modname, 'module', node_id, location=target)
+
+ # the platform and synopsis aren't printed; in fact, they are only
+ # used in the modindex currently
+ indextext = f'module; {modname}'
+ inode = addnodes.index(entries=[('pair', indextext, node_id, '', None)])
+ # The node order is: index node first, then target node.
+ ret.append(inode)
+ ret.append(target)
+ ret.extend(content_node.children)
+ return ret
+
+
+class PyCurrentModule(SphinxDirective):
+ """
+ This directive is just to tell Sphinx that we're documenting
+ stuff in module foo, but links to module foo won't lead here.
+ """
+
+ has_content = False
+ required_arguments = 1
+ optional_arguments = 0
+ final_argument_whitespace = False
+ option_spec: ClassVar[OptionSpec] = {}
+
+ def run(self) -> list[Node]:
+ modname = self.arguments[0].strip()
+ if modname == 'None':
+ self.env.ref_context.pop('py:module', None)
+ else:
+ self.env.ref_context['py:module'] = modname
+ return []
+
+
+class PyXRefRole(XRefRole):
+ def process_link(self, env: BuildEnvironment, refnode: Element,
+ has_explicit_title: bool, title: str, target: str) -> tuple[str, str]:
+ refnode['py:module'] = env.ref_context.get('py:module')
+ refnode['py:class'] = env.ref_context.get('py:class')
+ if not has_explicit_title:
+ title = title.lstrip('.') # only has a meaning for the target
+ 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[0:1] == '~':
+ title = title[1:]
+ dot = title.rfind('.')
+ if dot != -1:
+ title = title[dot + 1:]
+ # if the first character is a dot, search more specific namespaces first
+ # else search builtins first
+ if target[0:1] == '.':
+ target = target[1:]
+ refnode['refspecific'] = True
+ return title, target
+
+
+def filter_meta_fields(app: Sphinx, domain: str, objtype: str, content: Element) -> None:
+ """Filter ``:meta:`` field from its docstring."""
+ if domain != 'py':
+ return
+
+ for node in content:
+ if isinstance(node, nodes.field_list):
+ fields = cast(list[nodes.field], node)
+ # removing list items while iterating the list needs reversed()
+ for field in reversed(fields):
+ field_name = cast(nodes.field_body, field[0]).astext().strip()
+ if field_name == 'meta' or field_name.startswith('meta '):
+ node.remove(field)
+
+
+class PythonModuleIndex(Index):
+ """
+ Index subclass to provide the Python module index.
+ """
+
+ name = 'modindex'
+ localname = _('Python Module Index')
+ shortname = _('modules')
+
+ def generate(self, docnames: Iterable[str] | None = None,
+ ) -> tuple[list[tuple[str, list[IndexEntry]]], bool]:
+ content: dict[str, list[IndexEntry]] = {}
+ # list of prefixes to ignore
+ ignores: list[str] = self.domain.env.config['modindex_common_prefix']
+ ignores = sorted(ignores, key=len, reverse=True)
+ # list of all modules, sorted by module name
+ modules = sorted(self.domain.data['modules'].items(),
+ key=lambda x: x[0].lower())
+ # sort out collapsible modules
+ prev_modname = ''
+ num_toplevels = 0
+ for modname, (docname, node_id, synopsis, platforms, deprecated) in modules:
+ if docnames and docname not in docnames:
+ continue
+
+ for ignore in ignores:
+ if modname.startswith(ignore):
+ modname = modname[len(ignore):]
+ stripped = ignore
+ break
+ else:
+ stripped = ''
+
+ # we stripped the whole module name?
+ if not modname:
+ modname, stripped = stripped, ''
+
+ entries = content.setdefault(modname[0].lower(), [])
+
+ package = modname.split('.')[0]
+ if package != modname:
+ # it's a submodule
+ if prev_modname == package:
+ # first submodule - make parent a group head
+ if entries:
+ last = entries[-1]
+ entries[-1] = IndexEntry(last[0], 1, last[2], last[3],
+ last[4], last[5], last[6])
+ elif not prev_modname.startswith(package):
+ # submodule without parent in list, add dummy entry
+ entries.append(IndexEntry(stripped + package, 1, '', '', '', '', ''))
+ subtype = 2
+ else:
+ num_toplevels += 1
+ subtype = 0
+
+ qualifier = _('Deprecated') if deprecated else ''
+ entries.append(IndexEntry(stripped + modname, subtype, docname,
+ node_id, platforms, qualifier, synopsis))
+ prev_modname = modname
+
+ # apply heuristics when to collapse modindex at page load:
+ # only collapse if number of toplevel modules is larger than
+ # number of submodules
+ collapse = len(modules) - num_toplevels < num_toplevels
+
+ # sort by first letter
+ sorted_content = sorted(content.items())
+
+ return sorted_content, collapse
+
+
+class PythonDomain(Domain):
+ """Python language domain."""
+
+ name = 'py'
+ label = 'Python'
+ object_types: dict[str, ObjType] = {
+ 'function': ObjType(_('function'), 'func', 'obj'),
+ 'data': ObjType(_('data'), 'data', 'obj'),
+ 'class': ObjType(_('class'), 'class', 'exc', 'obj'),
+ 'exception': ObjType(_('exception'), 'exc', 'class', 'obj'),
+ 'method': ObjType(_('method'), 'meth', 'obj'),
+ 'classmethod': ObjType(_('class method'), 'meth', 'obj'),
+ 'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
+ 'attribute': ObjType(_('attribute'), 'attr', 'obj'),
+ 'property': ObjType(_('property'), 'attr', '_prop', 'obj'),
+ 'module': ObjType(_('module'), 'mod', 'obj'),
+ }
+
+ directives = {
+ 'function': PyFunction,
+ 'data': PyVariable,
+ 'class': PyClasslike,
+ 'exception': PyClasslike,
+ 'method': PyMethod,
+ 'classmethod': PyClassMethod,
+ 'staticmethod': PyStaticMethod,
+ 'attribute': PyAttribute,
+ 'property': PyProperty,
+ 'module': PyModule,
+ 'currentmodule': PyCurrentModule,
+ 'decorator': PyDecoratorFunction,
+ 'decoratormethod': PyDecoratorMethod,
+ }
+ roles = {
+ 'data': PyXRefRole(),
+ 'exc': PyXRefRole(),
+ 'func': PyXRefRole(fix_parens=True),
+ 'class': PyXRefRole(),
+ 'const': PyXRefRole(),
+ 'attr': PyXRefRole(),
+ 'meth': PyXRefRole(fix_parens=True),
+ 'mod': PyXRefRole(),
+ 'obj': PyXRefRole(),
+ }
+ initial_data: dict[str, dict[str, tuple[Any]]] = {
+ 'objects': {}, # fullname -> docname, objtype
+ 'modules': {}, # modname -> docname, synopsis, platform, deprecated
+ }
+ indices = [
+ PythonModuleIndex,
+ ]
+
+ @property
+ def objects(self) -> dict[str, ObjectEntry]:
+ return self.data.setdefault('objects', {}) # fullname -> ObjectEntry
+
+ def note_object(self, name: str, objtype: str, node_id: str,
+ aliased: bool = False, location: Any = None) -> None:
+ """Note a python object for cross reference.
+
+ .. versionadded:: 2.1
+ """
+ if name in self.objects:
+ other = self.objects[name]
+ if other.aliased and aliased is False:
+ # The original definition found. Override it!
+ pass
+ elif other.aliased is False and aliased:
+ # The original definition is already registered.
+ return
+ else:
+ # duplicated
+ logger.warning(__('duplicate object description of %s, '
+ 'other instance in %s, use :no-index: for one of them'),
+ name, other.docname, location=location)
+ self.objects[name] = ObjectEntry(self.env.docname, node_id, objtype, aliased)
+
+ @property
+ def modules(self) -> dict[str, ModuleEntry]:
+ return self.data.setdefault('modules', {}) # modname -> ModuleEntry
+
+ def note_module(self, name: str, node_id: str, synopsis: str,
+ platform: str, deprecated: bool) -> None:
+ """Note a python module for cross reference.
+
+ .. versionadded:: 2.1
+ """
+ self.modules[name] = ModuleEntry(self.env.docname, node_id,
+ synopsis, platform, deprecated)
+
+ def clear_doc(self, docname: str) -> None:
+ for fullname, obj in list(self.objects.items()):
+ if obj.docname == docname:
+ del self.objects[fullname]
+ for modname, mod in list(self.modules.items()):
+ if mod.docname == docname:
+ del self.modules[modname]
+
+ def merge_domaindata(self, docnames: list[str], otherdata: dict[str, Any]) -> None:
+ # XXX check duplicates?
+ for fullname, obj in otherdata['objects'].items():
+ if obj.docname in docnames:
+ self.objects[fullname] = obj
+ for modname, mod in otherdata['modules'].items():
+ if mod.docname in docnames:
+ self.modules[modname] = mod
+
+ def find_obj(self, env: BuildEnvironment, modname: str, classname: str,
+ name: str, type: str | None, searchmode: int = 0,
+ ) -> list[tuple[str, ObjectEntry]]:
+ """Find a Python object for "name", perhaps using the given module
+ and/or classname. Returns a list of (name, object entry) tuples.
+ """
+ # skip parens
+ if name[-2:] == '()':
+ name = name[:-2]
+
+ if not name:
+ return []
+
+ matches: list[tuple[str, ObjectEntry]] = []
+
+ newname = None
+ if searchmode == 1:
+ if type is None:
+ objtypes: list[str] | None = list(self.object_types)
+ else:
+ objtypes = self.objtypes_for_role(type)
+ if objtypes is not None:
+ if modname and classname:
+ fullname = modname + '.' + classname + '.' + name
+ if fullname in self.objects and self.objects[fullname].objtype in objtypes:
+ newname = fullname
+ if not newname:
+ if modname and modname + '.' + name in self.objects and \
+ self.objects[modname + '.' + name].objtype in objtypes:
+ newname = modname + '.' + name
+ elif name in self.objects and self.objects[name].objtype in objtypes:
+ newname = name
+ else:
+ # "fuzzy" searching mode
+ searchname = '.' + name
+ matches = [(oname, self.objects[oname]) for oname in self.objects
+ if oname.endswith(searchname) and
+ self.objects[oname].objtype in objtypes]
+ else:
+ # NOTE: searching for exact match, object type is not considered
+ if name in self.objects:
+ newname = name
+ elif type == 'mod':
+ # only exact matches allowed for modules
+ return []
+ elif classname and classname + '.' + name in self.objects:
+ newname = classname + '.' + name
+ elif modname and modname + '.' + name in self.objects:
+ newname = modname + '.' + name
+ elif modname and classname and \
+ modname + '.' + classname + '.' + name in self.objects:
+ newname = modname + '.' + classname + '.' + name
+ if newname is not None:
+ matches.append((newname, self.objects[newname]))
+ return matches
+
+ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
+ type: str, target: str, node: pending_xref, contnode: Element,
+ ) -> Element | None:
+ modname = node.get('py:module')
+ clsname = node.get('py:class')
+ searchmode = 1 if node.hasattr('refspecific') else 0
+ matches = self.find_obj(env, modname, clsname, target,
+ type, searchmode)
+
+ if not matches and type == 'attr':
+ # fallback to meth (for property; Sphinx 2.4.x)
+ # this ensures that `:attr:` role continues to refer to the old property entry
+ # that defined by ``method`` directive in old reST files.
+ matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode)
+ if not matches and type == 'meth':
+ # fallback to attr (for property)
+ # this ensures that `:meth:` in the old reST files can refer to the property
+ # entry that defined by ``property`` directive.
+ #
+ # Note: _prop is a secret role only for internal look-up.
+ matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode)
+
+ if not matches:
+ return None
+ elif len(matches) > 1:
+ canonicals = [m for m in matches if not m[1].aliased]
+ if len(canonicals) == 1:
+ matches = canonicals
+ else:
+ logger.warning(__('more than one target found for cross-reference %r: %s'),
+ target, ', '.join(match[0] for match in matches),
+ type='ref', subtype='python', location=node)
+ name, obj = matches[0]
+
+ if obj[2] == 'module':
+ return self._make_module_refnode(builder, fromdocname, name, contnode)
+ else:
+ # determine the content of the reference by conditions
+ content = find_pending_xref_condition(node, 'resolved')
+ if content:
+ children = content.children
+ else:
+ # if not found, use contnode
+ children = [contnode]
+
+ return make_refnode(builder, fromdocname, obj[0], obj[1], children, name)
+
+ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
+ target: str, node: pending_xref, contnode: Element,
+ ) -> list[tuple[str, Element]]:
+ modname = node.get('py:module')
+ clsname = node.get('py:class')
+ results: list[tuple[str, Element]] = []
+
+ # always search in "refspecific" mode with the :any: role
+ matches = self.find_obj(env, modname, clsname, target, None, 1)
+ multiple_matches = len(matches) > 1
+
+ for name, obj in matches:
+
+ if multiple_matches and obj.aliased:
+ # Skip duplicated matches
+ continue
+
+ if obj[2] == 'module':
+ results.append(('py:mod',
+ self._make_module_refnode(builder, fromdocname,
+ name, contnode)))
+ else:
+ # determine the content of the reference by conditions
+ content = find_pending_xref_condition(node, 'resolved')
+ if content:
+ children = content.children
+ else:
+ # if not found, use contnode
+ children = [contnode]
+
+ role = 'py:' + self.role_for_objtype(obj[2]) # type: ignore[operator]
+ results.append((role, make_refnode(builder, fromdocname, obj[0], obj[1],
+ children, name)))
+ return results
+
+ def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
+ contnode: Node) -> Element:
+ # get additional info for modules
+ module = self.modules[name]
+ title = name
+ if module.synopsis:
+ title += ': ' + module.synopsis
+ if module.deprecated:
+ title += _(' (deprecated)')
+ if module.platform:
+ title += ' (' + module.platform + ')'
+ return make_refnode(builder, fromdocname, module.docname, module.node_id,
+ contnode, title)
+
+ def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
+ for modname, mod in self.modules.items():
+ yield (modname, modname, 'module', mod.docname, mod.node_id, 0)
+ for refname, obj in self.objects.items():
+ if obj.objtype != 'module': # modules are already handled
+ if obj.aliased:
+ # aliased names are not full-text searchable.
+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1)
+ else:
+ yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1)
+
+ def get_full_qualified_name(self, node: Element) -> str | None:
+ modname = node.get('py:module')
+ clsname = node.get('py:class')
+ target = node.get('reftarget')
+ if target is None:
+ return None
+ else:
+ return '.'.join(filter(None, [modname, clsname, target]))
+
+
+def builtin_resolver(app: Sphinx, env: BuildEnvironment,
+ node: pending_xref, contnode: Element) -> Element | None:
+ """Do not emit nitpicky warnings for built-in types."""
+ def istyping(s: str) -> bool:
+ if s.startswith('typing.'):
+ s = s.split('.', 1)[1]
+
+ return s in typing.__all__
+
+ if node.get('refdomain') != 'py':
+ return None
+ elif node.get('reftype') in ('class', 'obj') and node.get('reftarget') == 'None':
+ return contnode
+ elif node.get('reftype') in ('class', 'obj', 'exc'):
+ reftarget = node.get('reftarget')
+ if inspect.isclass(getattr(builtins, reftarget, None)):
+ # built-in class
+ return contnode
+ if istyping(reftarget):
+ # typing class
+ return contnode
+
+ return None
+
+
+def setup(app: Sphinx) -> ExtensionMetadata:
+ app.setup_extension('sphinx.directives')
+
+ app.add_domain(PythonDomain)
+ app.add_config_value('python_use_unqualified_type_names', False, 'env')
+ app.add_config_value(
+ 'python_maximum_signature_line_length', None, 'env', {int, type(None)},
+ )
+ app.add_config_value('python_display_short_literal_types', False, 'env')
+ app.connect('object-description-transform', filter_meta_fields)
+ app.connect('missing-reference', builtin_resolver, priority=900)
+
+ return {
+ 'version': 'builtin',
+ 'env_version': 4,
+ 'parallel_read_safe': True,
+ 'parallel_write_safe': True,
+ }
diff --git a/sphinx/domains/python/_annotations.py b/sphinx/domains/python/_annotations.py
new file mode 100644
index 0000000..5d4803c
--- /dev/null
+++ b/sphinx/domains/python/_annotations.py
@@ -0,0 +1,507 @@
+from __future__ import annotations
+
+import ast
+import functools
+import operator
+import token
+from collections import deque
+from inspect import Parameter
+from typing import TYPE_CHECKING, Any
+
+from docutils import nodes
+
+from sphinx import addnodes
+from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
+from sphinx.pycode.parser import Token, TokenProcessor
+from sphinx.util.inspect import signature_from_str
+
+if TYPE_CHECKING:
+ from collections.abc import Iterable, Iterator
+
+ from docutils.nodes import Element, Node
+
+ from sphinx.environment import BuildEnvironment
+
+
+def parse_reftarget(reftarget: str, suppress_prefix: bool = False,
+ ) -> tuple[str, str, str, bool]:
+ """Parse a type string and return (reftype, reftarget, title, refspecific flag)"""
+ refspecific = False
+ if reftarget.startswith('.'):
+ reftarget = reftarget[1:]
+ title = reftarget
+ refspecific = True
+ elif reftarget.startswith('~'):
+ reftarget = reftarget[1:]
+ title = reftarget.split('.')[-1]
+ elif suppress_prefix:
+ title = reftarget.split('.')[-1]
+ elif reftarget.startswith('typing.'):
+ title = reftarget[7:]
+ else:
+ title = reftarget
+
+ if reftarget == 'None' or reftarget.startswith('typing.'):
+ # typing module provides non-class types. Obj reference is good to refer them.
+ reftype = 'obj'
+ else:
+ reftype = 'class'
+
+ return reftype, reftarget, title, refspecific
+
+
+def type_to_xref(target: str, env: BuildEnvironment, *,
+ suppress_prefix: bool = False) -> addnodes.pending_xref:
+ """Convert a type string to a cross reference node."""
+ if env:
+ kwargs = {'py:module': env.ref_context.get('py:module'),
+ 'py:class': env.ref_context.get('py:class')}
+ else:
+ kwargs = {}
+
+ reftype, target, title, refspecific = parse_reftarget(target, suppress_prefix)
+
+ if env.config.python_use_unqualified_type_names:
+ # Note: It would be better to use qualname to describe the object to support support
+ # nested classes. But python domain can't access the real python object because this
+ # module should work not-dynamically.
+ shortname = title.split('.')[-1]
+ contnodes: list[Node] = [pending_xref_condition('', shortname, condition='resolved'),
+ pending_xref_condition('', title, condition='*')]
+ else:
+ contnodes = [nodes.Text(title)]
+
+ return pending_xref('', *contnodes,
+ refdomain='py', reftype=reftype, reftarget=target,
+ refspecific=refspecific, **kwargs)
+
+
+def _parse_annotation(annotation: str, env: BuildEnvironment) -> list[Node]:
+ """Parse type annotation."""
+ short_literals = env.config.python_display_short_literal_types
+
+ def unparse(node: ast.AST) -> list[Node]:
+ if isinstance(node, ast.Attribute):
+ return [nodes.Text(f"{unparse(node.value)[0]}.{node.attr}")]
+ if isinstance(node, ast.BinOp):
+ result: list[Node] = unparse(node.left)
+ result.extend(unparse(node.op))
+ result.extend(unparse(node.right))
+ return result
+ if isinstance(node, ast.BitOr):
+ return [addnodes.desc_sig_space(),
+ addnodes.desc_sig_punctuation('', '|'),
+ addnodes.desc_sig_space()]
+ if isinstance(node, ast.Constant):
+ if node.value is Ellipsis:
+ return [addnodes.desc_sig_punctuation('', "...")]
+ if isinstance(node.value, bool):
+ return [addnodes.desc_sig_keyword('', repr(node.value))]
+ if isinstance(node.value, int):
+ return [addnodes.desc_sig_literal_number('', repr(node.value))]
+ if isinstance(node.value, str):
+ return [addnodes.desc_sig_literal_string('', repr(node.value))]
+ else:
+ # handles None, which is further handled by type_to_xref later
+ # and fallback for other types that should be converted
+ return [nodes.Text(repr(node.value))]
+ if isinstance(node, ast.Expr):
+ return unparse(node.value)
+ if isinstance(node, ast.Invert):
+ return [addnodes.desc_sig_punctuation('', '~')]
+ if isinstance(node, ast.USub):
+ return [addnodes.desc_sig_punctuation('', '-')]
+ if isinstance(node, ast.List):
+ result = [addnodes.desc_sig_punctuation('', '[')]
+ if node.elts:
+ # check if there are elements in node.elts to only pop the
+ # last element of result if the for-loop was run at least
+ # once
+ for elem in node.elts:
+ result.extend(unparse(elem))
+ result.append(addnodes.desc_sig_punctuation('', ','))
+ result.append(addnodes.desc_sig_space())
+ result.pop()
+ result.pop()
+ result.append(addnodes.desc_sig_punctuation('', ']'))
+ return result
+ if isinstance(node, ast.Module):
+ return functools.reduce(operator.iadd, (unparse(e) for e in node.body), [])
+ if isinstance(node, ast.Name):
+ return [nodes.Text(node.id)]
+ if isinstance(node, ast.Subscript):
+ if getattr(node.value, 'id', '') in {'Optional', 'Union'}:
+ return _unparse_pep_604_annotation(node)
+ if short_literals and getattr(node.value, 'id', '') == 'Literal':
+ return _unparse_pep_604_annotation(node)
+ result = unparse(node.value)
+ result.append(addnodes.desc_sig_punctuation('', '['))
+ result.extend(unparse(node.slice))
+ result.append(addnodes.desc_sig_punctuation('', ']'))
+
+ # Wrap the Text nodes inside brackets by literal node if the subscript is a Literal
+ if result[0] in ('Literal', 'typing.Literal'):
+ for i, subnode in enumerate(result[1:], start=1):
+ if isinstance(subnode, nodes.Text):
+ result[i] = nodes.literal('', '', subnode)
+ return result
+ if isinstance(node, ast.UnaryOp):
+ return unparse(node.op) + unparse(node.operand)
+ if isinstance(node, ast.Tuple):
+ if node.elts:
+ result = []
+ for elem in node.elts:
+ result.extend(unparse(elem))
+ result.append(addnodes.desc_sig_punctuation('', ','))
+ result.append(addnodes.desc_sig_space())
+ result.pop()
+ result.pop()
+ else:
+ result = [addnodes.desc_sig_punctuation('', '('),
+ addnodes.desc_sig_punctuation('', ')')]
+
+ return result
+ raise SyntaxError # unsupported syntax
+
+ def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]:
+ subscript = node.slice
+
+ flattened: list[Node] = []
+ if isinstance(subscript, ast.Tuple):
+ flattened.extend(unparse(subscript.elts[0]))
+ for elt in subscript.elts[1:]:
+ flattened.extend(unparse(ast.BitOr()))
+ flattened.extend(unparse(elt))
+ else:
+ # e.g. a Union[] inside an Optional[]
+ flattened.extend(unparse(subscript))
+
+ if getattr(node.value, 'id', '') == 'Optional':
+ flattened.extend(unparse(ast.BitOr()))
+ flattened.append(nodes.Text('None'))
+
+ return flattened
+
+ try:
+ tree = ast.parse(annotation, type_comments=True)
+ result: list[Node] = []
+ for node in unparse(tree):
+ if isinstance(node, nodes.literal):
+ result.append(node[0])
+ elif isinstance(node, nodes.Text) and node.strip():
+ if (result and isinstance(result[-1], addnodes.desc_sig_punctuation) and
+ result[-1].astext() == '~'):
+ result.pop()
+ result.append(type_to_xref(str(node), env, suppress_prefix=True))
+ else:
+ result.append(type_to_xref(str(node), env))
+ else:
+ result.append(node)
+ return result
+ except SyntaxError:
+ return [type_to_xref(annotation, env)]
+
+
+class _TypeParameterListParser(TokenProcessor):
+ def __init__(self, sig: str) -> None:
+ signature = sig.replace('\n', '').strip()
+ super().__init__([signature])
+ # Each item is a tuple (name, kind, default, annotation) mimicking
+ # ``inspect.Parameter`` to allow default values on VAR_POSITIONAL
+ # or VAR_KEYWORD parameters.
+ self.type_params: list[tuple[str, int, Any, Any]] = []
+
+ def fetch_type_param_spec(self) -> list[Token]:
+ tokens = []
+ while current := self.fetch_token():
+ tokens.append(current)
+ for ldelim, rdelim in ('(', ')'), ('{', '}'), ('[', ']'):
+ if current == [token.OP, ldelim]:
+ tokens += self.fetch_until([token.OP, rdelim])
+ break
+ else:
+ if current == token.INDENT:
+ tokens += self.fetch_until(token.DEDENT)
+ elif current.match(
+ [token.OP, ':'], [token.OP, '='], [token.OP, ',']):
+ tokens.pop()
+ break
+ return tokens
+
+ def parse(self) -> None:
+ while current := self.fetch_token():
+ if current == token.NAME:
+ tp_name = current.value.strip()
+ if self.previous and self.previous.match([token.OP, '*'], [token.OP, '**']):
+ if self.previous == [token.OP, '*']:
+ tp_kind = Parameter.VAR_POSITIONAL
+ else:
+ tp_kind = Parameter.VAR_KEYWORD # type: ignore[assignment]
+ else:
+ tp_kind = Parameter.POSITIONAL_OR_KEYWORD # type: ignore[assignment]
+
+ tp_ann: Any = Parameter.empty
+ tp_default: Any = Parameter.empty
+
+ current = self.fetch_token()
+ if current and current.match([token.OP, ':'], [token.OP, '=']):
+ if current == [token.OP, ':']:
+ tokens = self.fetch_type_param_spec()
+ tp_ann = self._build_identifier(tokens)
+
+ if self.current and self.current == [token.OP, '=']:
+ tokens = self.fetch_type_param_spec()
+ tp_default = self._build_identifier(tokens)
+
+ if tp_kind != Parameter.POSITIONAL_OR_KEYWORD and tp_ann != Parameter.empty:
+ msg = ('type parameter bound or constraint is not allowed '
+ f'for {tp_kind.description} parameters')
+ raise SyntaxError(msg)
+
+ type_param = (tp_name, tp_kind, tp_default, tp_ann)
+ self.type_params.append(type_param)
+
+ def _build_identifier(self, tokens: list[Token]) -> str:
+ from itertools import chain, islice
+
+ def triplewise(iterable: Iterable[Token]) -> Iterator[tuple[Token, ...]]:
+ # sliding_window('ABCDEFG', 4) --> ABCD BCDE CDEF DEFG
+ it = iter(iterable)
+ window = deque(islice(it, 3), maxlen=3)
+ if len(window) == 3:
+ yield tuple(window)
+ for x in it:
+ window.append(x)
+ yield tuple(window)
+
+ idents: list[str] = []
+ tokens: Iterable[Token] = iter(tokens) # type: ignore[no-redef]
+ # do not format opening brackets
+ for tok in tokens:
+ if not tok.match([token.OP, '('], [token.OP, '['], [token.OP, '{']):
+ # check if the first non-delimiter character is an unpack operator
+ is_unpack_operator = tok.match([token.OP, '*'], [token.OP, ['**']])
+ idents.append(self._pformat_token(tok, native=is_unpack_operator))
+ break
+ idents.append(tok.value)
+
+ # check the remaining tokens
+ stop = Token(token.ENDMARKER, '', (-1, -1), (-1, -1), '<sentinel>')
+ is_unpack_operator = False
+ for tok, op, after in triplewise(chain(tokens, [stop, stop])):
+ ident = self._pformat_token(tok, native=is_unpack_operator)
+ idents.append(ident)
+ # determine if the next token is an unpack operator depending
+ # on the left and right hand side of the operator symbol
+ is_unpack_operator = (
+ op.match([token.OP, '*'], [token.OP, '**']) and not (
+ tok.match(token.NAME, token.NUMBER, token.STRING,
+ [token.OP, ')'], [token.OP, ']'], [token.OP, '}'])
+ and after.match(token.NAME, token.NUMBER, token.STRING,
+ [token.OP, '('], [token.OP, '['], [token.OP, '{'])
+ )
+ )
+
+ return ''.join(idents).strip()
+
+ def _pformat_token(self, tok: Token, native: bool = False) -> str:
+ if native:
+ return tok.value
+
+ if tok.match(token.NEWLINE, token.ENDMARKER):
+ return ''
+
+ if tok.match([token.OP, ':'], [token.OP, ','], [token.OP, '#']):
+ return f'{tok.value} '
+
+ # Arithmetic operators are allowed because PEP 695 specifies the
+ # default type parameter to be *any* expression (so "T1 << T2" is
+ # allowed if it makes sense). The caller is responsible to ensure
+ # that a multiplication operator ("*") is not to be confused with
+ # an unpack operator (which will not be surrounded by spaces).
+ #
+ # The operators are ordered according to how likely they are to
+ # be used and for (possible) future implementations (e.g., "&" for
+ # an intersection type).
+ if tok.match(
+ # Most likely operators to appear
+ [token.OP, '='], [token.OP, '|'],
+ # Type composition (future compatibility)
+ [token.OP, '&'], [token.OP, '^'], [token.OP, '<'], [token.OP, '>'],
+ # Unlikely type composition
+ [token.OP, '+'], [token.OP, '-'], [token.OP, '*'], [token.OP, '**'],
+ # Unlikely operators but included for completeness
+ [token.OP, '@'], [token.OP, '/'], [token.OP, '//'], [token.OP, '%'],
+ [token.OP, '<<'], [token.OP, '>>'], [token.OP, '>>>'],
+ [token.OP, '<='], [token.OP, '>='], [token.OP, '=='], [token.OP, '!='],
+ ):
+ return f' {tok.value} '
+
+ return tok.value
+
+
+def _parse_type_list(
+ tp_list: str, env: BuildEnvironment,
+ multi_line_parameter_list: bool = False,
+) -> addnodes.desc_type_parameter_list:
+ """Parse a list of type parameters according to PEP 695."""
+ type_params = addnodes.desc_type_parameter_list(tp_list)
+ type_params['multi_line_parameter_list'] = multi_line_parameter_list
+ # formal parameter names are interpreted as type parameter names and
+ # type annotations are interpreted as type parameter bound or constraints
+ parser = _TypeParameterListParser(tp_list)
+ parser.parse()
+ for (tp_name, tp_kind, tp_default, tp_ann) in parser.type_params:
+ # no positional-only or keyword-only allowed in a type parameters list
+ if tp_kind in {Parameter.POSITIONAL_ONLY, Parameter.KEYWORD_ONLY}:
+ msg = ('positional-only or keyword-only parameters '
+ 'are prohibited in type parameter lists')
+ raise SyntaxError(msg)
+
+ node = addnodes.desc_type_parameter()
+ if tp_kind == Parameter.VAR_POSITIONAL:
+ node += addnodes.desc_sig_operator('', '*')
+ elif tp_kind == Parameter.VAR_KEYWORD:
+ node += addnodes.desc_sig_operator('', '**')
+ node += addnodes.desc_sig_name('', tp_name)
+
+ if tp_ann is not Parameter.empty:
+ annotation = _parse_annotation(tp_ann, env)
+ if not annotation:
+ continue
+
+ node += addnodes.desc_sig_punctuation('', ':')
+ node += addnodes.desc_sig_space()
+
+ type_ann_expr = addnodes.desc_sig_name('', '',
+ *annotation) # type: ignore[arg-type]
+ # a type bound is ``T: U`` whereas type constraints
+ # must be enclosed with parentheses. ``T: (U, V)``
+ if tp_ann.startswith('(') and tp_ann.endswith(')'):
+ type_ann_text = type_ann_expr.astext()
+ if type_ann_text.startswith('(') and type_ann_text.endswith(')'):
+ node += type_ann_expr
+ else:
+ # surrounding braces are lost when using _parse_annotation()
+ node += addnodes.desc_sig_punctuation('', '(')
+ node += type_ann_expr # type constraint
+ node += addnodes.desc_sig_punctuation('', ')')
+ else:
+ node += type_ann_expr # type bound
+
+ if tp_default is not Parameter.empty:
+ # Always surround '=' with spaces, even if there is no annotation
+ node += addnodes.desc_sig_space()
+ node += addnodes.desc_sig_operator('', '=')
+ node += addnodes.desc_sig_space()
+ node += nodes.inline('', tp_default,
+ classes=['default_value'],
+ support_smartquotes=False)
+
+ type_params += node
+ return type_params
+
+
+def _parse_arglist(
+ arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False,
+) -> addnodes.desc_parameterlist:
+ """Parse a list of arguments using AST parser"""
+ params = addnodes.desc_parameterlist(arglist)
+ params['multi_line_parameter_list'] = multi_line_parameter_list
+ sig = signature_from_str('(%s)' % arglist)
+ last_kind = None
+ for param in sig.parameters.values():
+ if param.kind != param.POSITIONAL_ONLY and last_kind == param.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
+ if param.kind == param.KEYWORD_ONLY and last_kind in (param.POSITIONAL_OR_KEYWORD,
+ param.POSITIONAL_ONLY,
+ None):
+ # PEP-3102: Separator for Keyword Only Parameter: *
+ params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '*'))
+
+ node = addnodes.desc_parameter()
+ if param.kind == param.VAR_POSITIONAL:
+ node += addnodes.desc_sig_operator('', '*')
+ node += addnodes.desc_sig_name('', param.name)
+ elif param.kind == param.VAR_KEYWORD:
+ node += addnodes.desc_sig_operator('', '**')
+ node += addnodes.desc_sig_name('', param.name)
+ else:
+ node += addnodes.desc_sig_name('', param.name)
+
+ if param.annotation is not param.empty:
+ children = _parse_annotation(param.annotation, env)
+ node += addnodes.desc_sig_punctuation('', ':')
+ node += addnodes.desc_sig_space()
+ node += addnodes.desc_sig_name('', '', *children) # type: ignore[arg-type]
+ if param.default is not param.empty:
+ if param.annotation is not param.empty:
+ node += addnodes.desc_sig_space()
+ node += addnodes.desc_sig_operator('', '=')
+ node += addnodes.desc_sig_space()
+ else:
+ node += addnodes.desc_sig_operator('', '=')
+ node += nodes.inline('', param.default, classes=['default_value'],
+ support_smartquotes=False)
+
+ params += node
+ last_kind = param.kind
+
+ if last_kind == Parameter.POSITIONAL_ONLY:
+ # PEP-570: Separator for Positional Only Parameter: /
+ params += addnodes.desc_parameter('', '', addnodes.desc_sig_operator('', '/'))
+
+ return params
+
+
+def _pseudo_parse_arglist(
+ signode: desc_signature, arglist: str, multi_line_parameter_list: bool = False,
+) -> None:
+ """"Parse" a list of arguments separated by commas.
+
+ Arguments can have "optional" annotations given by enclosing them in
+ brackets. Currently, this will split at any comma, even if it's inside a
+ string literal (e.g. default argument value).
+ """
+ paramlist = addnodes.desc_parameterlist()
+ paramlist['multi_line_parameter_list'] = multi_line_parameter_list
+ stack: list[Element] = [paramlist]
+ try:
+ for argument in arglist.split(','):
+ argument = argument.strip()
+ ends_open = ends_close = 0
+ while argument.startswith('['):
+ stack.append(addnodes.desc_optional())
+ stack[-2] += stack[-1]
+ argument = argument[1:].strip()
+ while argument.startswith(']'):
+ stack.pop()
+ argument = argument[1:].strip()
+ while argument.endswith(']') and not argument.endswith('[]'):
+ ends_close += 1
+ argument = argument[:-1].strip()
+ while argument.endswith('['):
+ ends_open += 1
+ argument = argument[:-1].strip()
+ if argument:
+ stack[-1] += addnodes.desc_parameter(
+ '', '', addnodes.desc_sig_name(argument, argument))
+ while ends_open:
+ stack.append(addnodes.desc_optional())
+ stack[-2] += stack[-1]
+ ends_open -= 1
+ while ends_close:
+ stack.pop()
+ ends_close -= 1
+ if len(stack) != 1:
+ raise IndexError
+ except IndexError:
+ # if there are too few or too many elements on the stack, just give up
+ # and treat the whole argument list as one argument, discarding the
+ # already partially populated paramlist node
+ paramlist = addnodes.desc_parameterlist()
+ paramlist += addnodes.desc_parameter(arglist, arglist)
+ signode += paramlist
+ else:
+ signode += paramlist
diff --git a/sphinx/domains/python/_object.py b/sphinx/domains/python/_object.py
new file mode 100644
index 0000000..41f9df1
--- /dev/null
+++ b/sphinx/domains/python/_object.py
@@ -0,0 +1,426 @@
+from __future__ import annotations
+
+import contextlib
+import re
+from typing import TYPE_CHECKING, ClassVar
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+
+from sphinx import addnodes
+from sphinx.addnodes import desc_signature, pending_xref, pending_xref_condition
+from sphinx.directives import ObjectDescription
+from sphinx.domains.python._annotations import (
+ _parse_annotation,
+ _parse_arglist,
+ _parse_type_list,
+ _pseudo_parse_arglist,
+ parse_reftarget,
+)
+from sphinx.locale import _
+from sphinx.util import logging
+from sphinx.util.docfields import Field, GroupedField, TypedField
+from sphinx.util.nodes import (
+ make_id,
+)
+
+if TYPE_CHECKING:
+
+ from docutils.nodes import Node
+ from docutils.parsers.rst.states import Inliner
+
+ from sphinx.environment import BuildEnvironment
+ from sphinx.util.typing import OptionSpec, TextlikeNode
+
+logger = logging.getLogger(__name__)
+
+# REs for Python signatures
+py_sig_re = re.compile(
+ r'''^ ([\w.]*\.)? # class name(s)
+ (\w+) \s* # thing name
+ (?: \[\s*(.*)\s*])? # optional: type parameters list
+ (?: \(\s*(.*)\s*\) # optional: arguments
+ (?:\s* -> \s* (.*))? # return annotation
+ )? $ # and nothing more
+ ''', re.VERBOSE)
+
+
+# This override allows our inline type specifiers to behave like :class: link
+# when it comes to handling "." and "~" prefixes.
+class PyXrefMixin:
+ def make_xref(
+ self,
+ rolename: str,
+ domain: str,
+ target: str,
+ innernode: type[TextlikeNode] = nodes.emphasis,
+ contnode: Node | None = None,
+ env: BuildEnvironment | None = None,
+ inliner: Inliner | None = None,
+ location: Node | None = None,
+ ) -> Node:
+ # we use inliner=None to make sure we get the old behaviour with a single
+ # pending_xref node
+ result = super().make_xref(rolename, domain, target, # type: ignore[misc]
+ innernode, contnode,
+ env, inliner=None, location=None)
+ if isinstance(result, pending_xref):
+ assert env is not None
+ result['refspecific'] = True
+ result['py:module'] = env.ref_context.get('py:module')
+ result['py:class'] = env.ref_context.get('py:class')
+
+ reftype, reftarget, reftitle, _ = parse_reftarget(target)
+ if reftarget != reftitle:
+ result['reftype'] = reftype
+ result['reftarget'] = reftarget
+
+ result.clear()
+ result += innernode(reftitle, reftitle) # type: ignore[call-arg]
+ elif env.config.python_use_unqualified_type_names:
+ children = result.children
+ result.clear()
+
+ shortname = target.split('.')[-1]
+ textnode = innernode('', shortname) # type: ignore[call-arg]
+ contnodes = [pending_xref_condition('', '', textnode, condition='resolved'),
+ pending_xref_condition('', '', *children, condition='*')]
+ result.extend(contnodes)
+
+ return result
+
+ def make_xrefs(
+ self,
+ rolename: str,
+ domain: str,
+ target: str,
+ innernode: type[TextlikeNode] = nodes.emphasis,
+ contnode: Node | None = None,
+ env: BuildEnvironment | None = None,
+ inliner: Inliner | None = None,
+ location: Node | None = None,
+ ) -> list[Node]:
+ delims = r'(\s*[\[\]\(\),](?:\s*o[rf]\s)?\s*|\s+o[rf]\s+|\s*\|\s*|\.\.\.)'
+ delims_re = re.compile(delims)
+ sub_targets = re.split(delims, target)
+
+ split_contnode = bool(contnode and contnode.astext() == target)
+
+ in_literal = False
+ results = []
+ for sub_target in filter(None, sub_targets):
+ if split_contnode:
+ contnode = nodes.Text(sub_target)
+
+ if in_literal or delims_re.match(sub_target):
+ results.append(contnode or innernode(sub_target, sub_target)) # type: ignore[call-arg]
+ else:
+ results.append(self.make_xref(rolename, domain, sub_target,
+ innernode, contnode, env, inliner, location))
+
+ if sub_target in ('Literal', 'typing.Literal', '~typing.Literal'):
+ in_literal = True
+
+ return results
+
+
+class PyField(PyXrefMixin, Field):
+ pass
+
+
+class PyGroupedField(PyXrefMixin, GroupedField):
+ pass
+
+
+class PyTypedField(PyXrefMixin, TypedField):
+ pass
+
+
+class PyObject(ObjectDescription[tuple[str, str]]):
+ """
+ Description of a general Python object.
+
+ :cvar allow_nesting: Class is an object that allows for nested namespaces
+ :vartype allow_nesting: bool
+ """
+
+ option_spec: ClassVar[OptionSpec] = {
+ 'no-index': directives.flag,
+ 'no-index-entry': directives.flag,
+ 'no-contents-entry': directives.flag,
+ 'no-typesetting': directives.flag,
+ 'noindex': directives.flag,
+ 'noindexentry': directives.flag,
+ 'nocontentsentry': directives.flag,
+ 'single-line-parameter-list': directives.flag,
+ 'single-line-type-parameter-list': directives.flag,
+ 'module': directives.unchanged,
+ 'canonical': directives.unchanged,
+ 'annotation': directives.unchanged,
+ }
+
+ doc_field_types = [
+ PyTypedField('parameter', label=_('Parameters'),
+ names=('param', 'parameter', 'arg', 'argument',
+ 'keyword', 'kwarg', 'kwparam'),
+ typerolename='class', typenames=('paramtype', 'type'),
+ can_collapse=True),
+ PyTypedField('variable', label=_('Variables'),
+ names=('var', 'ivar', 'cvar'),
+ typerolename='class', typenames=('vartype',),
+ can_collapse=True),
+ PyGroupedField('exceptions', label=_('Raises'), rolename='exc',
+ names=('raises', 'raise', 'exception', 'except'),
+ can_collapse=True),
+ Field('returnvalue', label=_('Returns'), has_arg=False,
+ names=('returns', 'return')),
+ PyField('returntype', label=_('Return type'), has_arg=False,
+ names=('rtype',), bodyrolename='class'),
+ ]
+
+ allow_nesting = False
+
+ def get_signature_prefix(self, sig: str) -> list[nodes.Node]:
+ """May return a prefix to put before the object name in the
+ signature.
+ """
+ return []
+
+ def needs_arglist(self) -> bool:
+ """May return true if an empty argument list is to be generated even if
+ the document contains none.
+ """
+ return False
+
+ def handle_signature(self, sig: str, signode: desc_signature) -> tuple[str, str]:
+ """Transform a Python signature into RST nodes.
+
+ Return (fully qualified name of the thing, classname if any).
+
+ If inside a class, the current class name is handled intelligently:
+ * it is stripped from the displayed name if present
+ * it is added to the full name (return value) if not present
+ """
+ m = py_sig_re.match(sig)
+ if m is None:
+ raise ValueError
+ prefix, name, tp_list, arglist, retann = m.groups()
+
+ # determine module and class name (if applicable), as well as full name
+ modname = self.options.get('module', self.env.ref_context.get('py:module'))
+ classname = self.env.ref_context.get('py:class')
+ if classname:
+ add_module = False
+ if prefix and (prefix == classname or
+ prefix.startswith(classname + ".")):
+ fullname = prefix + name
+ # class name is given again in the signature
+ prefix = prefix[len(classname):].lstrip('.')
+ elif prefix:
+ # class name is given in the signature, but different
+ # (shouldn't happen)
+ fullname = classname + '.' + prefix + name
+ else:
+ # class name is not given in the signature
+ fullname = classname + '.' + name
+ else:
+ add_module = True
+ if prefix:
+ classname = prefix.rstrip('.')
+ fullname = prefix + name
+ else:
+ classname = ''
+ fullname = name
+
+ signode['module'] = modname
+ signode['class'] = classname
+ signode['fullname'] = fullname
+
+ max_len = (self.env.config.python_maximum_signature_line_length
+ or self.env.config.maximum_signature_line_length
+ or 0)
+
+ # determine if the function arguments (without its type parameters)
+ # should be formatted on a multiline or not by removing the width of
+ # the type parameters list (if any)
+ sig_len = len(sig)
+ tp_list_span = m.span(3)
+ multi_line_parameter_list = (
+ 'single-line-parameter-list' not in self.options
+ and (sig_len - (tp_list_span[1] - tp_list_span[0])) > max_len > 0
+ )
+
+ # determine whether the type parameter list must be wrapped or not
+ arglist_span = m.span(4)
+ multi_line_type_parameter_list = (
+ 'single-line-type-parameter-list' not in self.options
+ and (sig_len - (arglist_span[1] - arglist_span[0])) > max_len > 0
+ )
+
+ sig_prefix = self.get_signature_prefix(sig)
+ if sig_prefix:
+ if type(sig_prefix) is str:
+ msg = ("Python directive method get_signature_prefix()"
+ " must return a list of nodes."
+ f" Return value was '{sig_prefix}'.")
+ raise TypeError(msg)
+ signode += addnodes.desc_annotation(str(sig_prefix), '', *sig_prefix)
+
+ if prefix:
+ signode += addnodes.desc_addname(prefix, prefix)
+ elif modname and add_module and self.env.config.add_module_names:
+ nodetext = modname + '.'
+ signode += addnodes.desc_addname(nodetext, nodetext)
+
+ signode += addnodes.desc_name(name, name)
+
+ if tp_list:
+ try:
+ signode += _parse_type_list(tp_list, self.env, multi_line_type_parameter_list)
+ except Exception as exc:
+ logger.warning("could not parse tp_list (%r): %s", tp_list, exc,
+ location=signode)
+
+ if arglist:
+ try:
+ signode += _parse_arglist(arglist, self.env, multi_line_parameter_list)
+ except SyntaxError:
+ # fallback to parse arglist original parser
+ # (this may happen if the argument list is incorrectly used
+ # as a list of bases when documenting a class)
+ # it supports to represent optional arguments (ex. "func(foo [, bar])")
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
+ except (NotImplementedError, ValueError) as exc:
+ # duplicated parameter names raise ValueError and not a SyntaxError
+ logger.warning("could not parse arglist (%r): %s", arglist, exc,
+ location=signode)
+ _pseudo_parse_arglist(signode, arglist, multi_line_parameter_list)
+ else:
+ if self.needs_arglist():
+ # for callables, add an empty parameter list
+ signode += addnodes.desc_parameterlist()
+
+ if retann:
+ children = _parse_annotation(retann, self.env)
+ signode += addnodes.desc_returns(retann, '', *children)
+
+ anno = self.options.get('annotation')
+ if anno:
+ signode += addnodes.desc_annotation(' ' + anno, '',
+ addnodes.desc_sig_space(),
+ nodes.Text(anno))
+
+ return fullname, prefix
+
+ def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]:
+ if 'fullname' not in sig_node:
+ return ()
+ modname = sig_node.get('module')
+ fullname = sig_node['fullname']
+
+ if modname:
+ return (modname, *fullname.split('.'))
+ else:
+ return tuple(fullname.split('.'))
+
+ def get_index_text(self, modname: str, name: tuple[str, str]) -> str:
+ """Return the text for the index entry of the object."""
+ msg = 'must be implemented in subclasses'
+ raise NotImplementedError(msg)
+
+ def add_target_and_index(self, name_cls: tuple[str, str], sig: str,
+ signode: desc_signature) -> None:
+ modname = self.options.get('module', self.env.ref_context.get('py:module'))
+ fullname = (modname + '.' if modname else '') + name_cls[0]
+ node_id = make_id(self.env, self.state.document, '', fullname)
+ signode['ids'].append(node_id)
+ self.state.document.note_explicit_target(signode)
+
+ domain = self.env.domains['py']
+ domain.note_object(fullname, self.objtype, node_id, location=signode)
+
+ canonical_name = self.options.get('canonical')
+ if canonical_name:
+ domain.note_object(canonical_name, self.objtype, node_id, aliased=True,
+ location=signode)
+
+ if 'no-index-entry' not in self.options:
+ indextext = self.get_index_text(modname, name_cls)
+ if indextext:
+ self.indexnode['entries'].append(('single', indextext, node_id, '', None))
+
+ def before_content(self) -> None:
+ """Handle object nesting before content
+
+ :py:class:`PyObject` represents Python language constructs. For
+ constructs that are nestable, such as a Python classes, this method will
+ build up a stack of the nesting hierarchy so that it can be later
+ de-nested correctly, in :py:meth:`after_content`.
+
+ For constructs that aren't nestable, the stack is bypassed, and instead
+ only the most recent object is tracked. This object prefix name will be
+ removed with :py:meth:`after_content`.
+ """
+ prefix = None
+ if self.names:
+ # fullname and name_prefix come from the `handle_signature` method.
+ # fullname represents the full object name that is constructed using
+ # object nesting and explicit prefixes. `name_prefix` is the
+ # explicit prefix given in a signature
+ (fullname, name_prefix) = self.names[-1]
+ if self.allow_nesting:
+ prefix = fullname
+ elif name_prefix:
+ prefix = name_prefix.strip('.')
+ if prefix:
+ self.env.ref_context['py:class'] = prefix
+ if self.allow_nesting:
+ classes = self.env.ref_context.setdefault('py:classes', [])
+ classes.append(prefix)
+ if 'module' in self.options:
+ modules = self.env.ref_context.setdefault('py:modules', [])
+ modules.append(self.env.ref_context.get('py:module'))
+ self.env.ref_context['py:module'] = self.options['module']
+
+ def after_content(self) -> None:
+ """Handle object de-nesting after content
+
+ If this class is a nestable object, removing the last nested class prefix
+ ends further nesting in the object.
+
+ If this class is not a nestable object, the list of classes should not
+ be altered as we didn't affect the nesting levels in
+ :py:meth:`before_content`.
+ """
+ classes = self.env.ref_context.setdefault('py:classes', [])
+ if self.allow_nesting:
+ with contextlib.suppress(IndexError):
+ classes.pop()
+
+ self.env.ref_context['py:class'] = (classes[-1] if len(classes) > 0
+ else None)
+ if 'module' in self.options:
+ modules = self.env.ref_context.setdefault('py:modules', [])
+ if modules:
+ self.env.ref_context['py:module'] = modules.pop()
+ else:
+ self.env.ref_context.pop('py:module')
+
+ 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 sig_node.get('fullname', 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 ''
diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py
index 480aba5..5ae267a 100644
--- a/sphinx/domains/rst.py
+++ b/sphinx/domains/rst.py
@@ -3,7 +3,7 @@
from __future__ import annotations
import re
-from typing import TYPE_CHECKING, Any, cast
+from typing import TYPE_CHECKING, Any, ClassVar, cast
from docutils.parsers.rst import directives
@@ -24,7 +24,7 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec
logger = logging.getLogger(__name__)
@@ -35,7 +35,8 @@ class ReSTMarkup(ObjectDescription[str]):
"""
Description of generic reST markup.
"""
- option_spec: OptionSpec = {
+
+ option_spec: ClassVar[OptionSpec] = {
'no-index': directives.flag,
'no-index-entry': directives.flag,
'no-contents-entry': directives.flag,
@@ -112,6 +113,7 @@ class ReSTDirective(ReSTMarkup):
"""
Description of a reST directive.
"""
+
def handle_signature(self, sig: str, signode: desc_signature) -> str:
name, args = parse_directive(sig)
desc_name = f'.. {name}::'
@@ -139,7 +141,8 @@ class ReSTDirectiveOption(ReSTMarkup):
"""
Description of an option for reST directive.
"""
- option_spec: OptionSpec = ReSTMarkup.option_spec.copy()
+
+ option_spec: ClassVar[OptionSpec] = ReSTMarkup.option_spec.copy()
option_spec.update({
'type': directives.unchanged,
})
@@ -165,8 +168,8 @@ class ReSTDirectiveOption(ReSTMarkup):
directive_name = self.current_directive
if directive_name:
- prefix = '-'.join([self.objtype, directive_name])
- objname = ':'.join([directive_name, name])
+ prefix = f'{self.objtype}-{directive_name}'
+ objname = f'{directive_name}:{name}'
else:
prefix = self.objtype
objname = name
@@ -199,6 +202,7 @@ class ReSTRole(ReSTMarkup):
"""
Description of a reST role.
"""
+
def handle_signature(self, sig: str, signode: desc_signature) -> str:
desc_name = f':{sig}:'
signode['fullname'] = sig.strip()
@@ -211,6 +215,7 @@ class ReSTRole(ReSTMarkup):
class ReSTDomain(Domain):
"""ReStructuredText domain."""
+
name = 'rst'
label = 'reStructuredText'
@@ -288,7 +293,7 @@ class ReSTDomain(Domain):
yield name, name, typ, docname, node_id, 1
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(ReSTDomain)
return {
diff --git a/sphinx/domains/std.py b/sphinx/domains/std/__init__.py
index b3082a7..30d0977 100644
--- a/sphinx/domains/std.py
+++ b/sphinx/domains/std/__init__.py
@@ -4,7 +4,7 @@ from __future__ import annotations
import re
from copy import copy
-from typing import TYPE_CHECKING, Any, Callable, Final, cast
+from typing import TYPE_CHECKING, Any, Callable, ClassVar, Final, cast
from docutils import nodes
from docutils.nodes import Element, Node, system_message
@@ -27,14 +27,14 @@ if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.builders import Builder
from sphinx.environment import BuildEnvironment
- from sphinx.util.typing import OptionSpec, RoleFunction
+ from sphinx.util.typing import ExtensionMetadata, OptionSpec, RoleFunction
logger = logging.getLogger(__name__)
# RE for option descriptions
option_desc_re = re.compile(r'((?:/|--|-|\+)?[^\s=]+)(=?\s*.*)')
# RE for grammar tokens
-token_re = re.compile(r'`((~?\w*:)?\w+)`', re.U)
+token_re = re.compile(r'`((~?[\w-]*:)?\w+)`')
samp_role = EmphasizedLiteral()
@@ -43,6 +43,7 @@ class GenericObject(ObjectDescription[str]):
"""
A generic x-ref directive registered with Sphinx.add_object_type().
"""
+
indextemplate: str = ''
parse_node: Callable[[BuildEnvironment, str, desc_signature], str] | None = None
@@ -104,13 +105,14 @@ class Target(SphinxDirective):
"""
Generic target for user-defined cross-reference types.
"""
+
indextemplate = ''
has_content = False
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
- option_spec: OptionSpec = {}
+ option_spec: ClassVar[OptionSpec] = {}
def run(self) -> list[Node]:
# normalize whitespace in fullname like XRefRole does
@@ -204,7 +206,7 @@ class Cmdoption(ObjectDescription[str]):
def add_target_and_index(self, firstname: str, sig: str, signode: desc_signature) -> None:
currprogram = self.env.ref_context.get('std:program')
- for optname in signode.get('allnames', []):
+ for optname in signode.get('allnames', []): # type: ignore[var-annotated]
prefixes = ['cmdoption']
if currprogram:
prefixes.append(currprogram)
@@ -226,8 +228,8 @@ class Cmdoption(ObjectDescription[str]):
descr = _('%s command line option') % currprogram
else:
descr = _('command line option')
- for option in signode.get('allnames', []):
- entry = '; '.join([descr, option])
+ for option in signode.get('allnames', []): # type: ignore[var-annotated]
+ entry = f'{descr}; {option}'
self.indexnode['entries'].append(('pair', entry, signode['ids'][0], '', None))
@@ -240,7 +242,7 @@ class Program(SphinxDirective):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
- option_spec: OptionSpec = {}
+ option_spec: ClassVar[OptionSpec] = {}
def run(self) -> list[Node]:
program = ws_re.sub('-', self.arguments[0].strip())
@@ -260,7 +262,7 @@ class OptionXRefRole(XRefRole):
def split_term_classifiers(line: str) -> list[str | None]:
# split line into a term and classifiers. if no classifier, None is used..
- parts: list[str | None] = re.split(' +: +', line) + [None]
+ parts: list[str | None] = [*re.split(' +: +', line), None]
return parts
@@ -304,7 +306,7 @@ class Glossary(SphinxDirective):
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = False
- option_spec: OptionSpec = {
+ option_spec: ClassVar[OptionSpec] = {
'sorted': directives.flag,
}
@@ -383,7 +385,7 @@ class Glossary(SphinxDirective):
parts = split_term_classifiers(line)
# parse the term with inline markup
# classifiers (parts[1:]) will not be shown on doctree
- textnodes, sysmsg = self.state.inline_text(parts[0], # type: ignore[arg-type]
+ textnodes, sysmsg = self.state.inline_text(parts[0],
lineno)
# use first classifier as a index key
@@ -406,7 +408,7 @@ class Glossary(SphinxDirective):
dlist = nodes.definition_list('', *items)
dlist['classes'].append('glossary')
node += dlist
- return messages + [node]
+ return [*messages, node]
def token_xrefs(text: str, productionGroup: str = '') -> list[Node]:
@@ -451,7 +453,7 @@ class ProductionList(SphinxDirective):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
- option_spec: OptionSpec = {}
+ option_spec: ClassVar[OptionSpec] = {}
def run(self) -> list[Node]:
domain = cast(StandardDomain, self.env.get_domain('std'))
@@ -1000,7 +1002,7 @@ class StandardDomain(Domain):
yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1)
for (prog, option), info in self.progoptions.items():
if prog:
- fullname = ".".join([prog, option])
+ fullname = f'{prog}.{option}'
yield (fullname, fullname, 'cmdoption', info[0], info[1], 1)
else:
yield (option, option, 'cmdoption', info[0], info[1], 1)
@@ -1089,7 +1091,8 @@ class StandardDomain(Domain):
command.insert(0, progname)
option = command.pop()
if command:
- return '.'.join(['-'.join(command), option])
+ command_str = '-'.join(command)
+ return f'{command_str}.{option}'
else:
return None
else:
@@ -1111,7 +1114,7 @@ def warn_missing_reference(app: Sphinx, domain: Domain, node: pending_xref,
return True
-def setup(app: Sphinx) -> dict[str, Any]:
+def setup(app: Sphinx) -> ExtensionMetadata:
app.add_domain(StandardDomain)
app.connect('warn-missing-reference', warn_missing_reference)