1"""The code a marker is attached to: what counts as an entity, and how one is named.
   2
   3A marker is attached to a definition node of the syntax tree (a function, a class,
   4a namespace, ...): its *entity*. This module holds the per-language tables that say
   5which node kinds are entities, and the one naming function, :func:`describe_entity`,
   6that turns such a node into its kind, its qualified name *within its file* (no
   7module or package prefix: that is derived from the file's path, which this module
   8never reads -- every function here is pure) and that name's components, its
   9``semantic``. :func:`render_semantic` joins the components back into the name by the
  10rule of the file's language (:func:`scope_language`), and :func:`name_chain` is what
  11identifies an entity among them: names, never kinds.
  12
  13The traversal that picks the node for a comment is ``find_associated_scope`` /
  14``associate`` in :mod:`sphinx_codelinks.analyse.utils`; this module only answers
  15questions about a node.
  16"""
  17
  18from __future__ import annotations
  19
  20import re
  21from collections.abc import Callable, Iterable, Sequence
  22from dataclasses import dataclass, field, replace
  23from pathlib import PurePath
  24from typing import Literal, NotRequired, TypedDict
  25
  26from tree_sitter import Node as TreeSitterNode
  27
  28from sphinx_codelinks.analyse.cpp_macros import MacroScopes
  29from sphinx_codelinks.source_discover.config import CommentType
  30
  31EntityKind = Literal[
  32    "function",
  33    "method",
  34    "constructor",
  35    "destructor",
  36    "operator",
  37    "class",
  38    "struct",
  39    "union",
  40    "enum",
  41    "interface",
  42    "trait",
  43    "impl",
  44    "record",
  45    "namespace",
  46    "module",
  47    "type",
  48    "macro",
  49    "property",
  50    "event",
  51    "indexer",
  52    "delegate",
  53    "key",
  54    "item",
  55]
  56"""The kind of an entity, as the analysis output spells it."""
  57
  58Mechanism = Literal["doc_comment", "leading", "trailing", "enclosing"]
  59"""How a marker came to be attached to its entity.
  60
  61``doc_comment`` is *declared*: a documentation comment (``///``, ``//!``, ``/**``,
  62``/*!``, a C# ``///``, a JSDoc ``/**``, a Python docstring) attached by its own rule.
  63The other three are *inferred* from where a plain comment sits: on the lines before the
  64entity (``leading``), after code on the entity's own line (``trailing``), or inside its
  65body (``enclosing``).
  66"""
  67
  68
  69class SemanticElement(TypedDict):
  70    """One component of an entity's qualified name: a container, or the entity."""
  71
  72    kind: str
  73    """What it is: an :data:`EntityKind`, or ``scope`` for a C++ qualifier the syntax
  74    cannot classify (the ``ns`` of ``void ns::W::f() {}``). A Rust impl is an
  75    ``impl``, a Go receiver's type a ``type``."""
  76    name: str
  77    """Its name, as the qualified name spells it: ``Box<bool>`` keeps the arguments of
  78    an explicit specialisation, an expression name (``module.exports.f``) is whole,
  79    and a Rust impl is its self type (``Widget``, ``dyn Shape``)."""
  80    trait: NotRequired[str]
  81    """The trait of a Rust trait impl, on its ``impl`` component."""
  82
  83
  84@dataclass(frozen=True)
  85class EntityName:
  86    """A definition node, named."""
  87
  88    kind: EntityKind
  89    """What the node defines."""
  90    qualified_name: str | None
  91    """The name within its file, qualified by the containers the file shows
  92    (``ns::Widget::draw``, ``Shape.area``, ``<Widget as Shape>::area``); ``None`` when
  93    the entity has no name (an anonymous class, a data-file entry)."""
  94    semantic: tuple[SemanticElement, ...] | None
  95    """``qualified_name`` as its components, outermost first and the entity last
  96    (``namespace ns``, ``class Widget``, ``method draw``); ``None`` exactly when
  97    ``qualified_name`` is. :func:`render_semantic` spells them back."""
  98    name_end: int | None = field(default=None, compare=False, repr=False)
  99    """The byte offset where the node's own name ends in the file (C and C++ only),
 100    ``None`` when it has no name node: what the names a whole C++ file decides are
 101    keyed by (:class:`CppNames`). Not part of the analysis output."""
 102
 103
 104@dataclass(frozen=True)
 105class Association:
 106    """The node a comment is attached to, and how."""
 107
 108    node: TreeSitterNode
 109    mechanism: Mechanism
 110
 111
 112class CodeScope(TypedDict):
 113    """The ``scope`` object of an analysis record: the entity a marker is attached to."""
 114
 115    kind: str
 116    """The tree-sitter node kind of the entity (``function_definition``)."""
 117    start: int
 118    """The node's first line (1-based)."""
 119    end: int
 120    """The node's last line (1-based)."""
 121    language: str
 122    """The file's language (:func:`scope_language`): the rule ``semantic`` renders by."""
 123    entity_kind: str | None
 124    """The entity's kind (:data:`EntityKind`), ``None`` if it could not be named."""
 125    qualified_name: str | None
 126    """The entity's in-file qualified name, ``None`` if it has none."""
 127    semantic: list[SemanticElement] | None
 128    """The qualified name's components (:class:`SemanticElement`), outermost first and
 129    the entity last; ``None`` exactly when ``qualified_name`` is."""
 130    mechanism: Mechanism
 131    """How the marker was attached (:data:`Mechanism`)."""
 132
 133
 134# ---------------------------------------------------------------------------
 135# What is an entity: the node kinds a marker can be attached to
 136# ---------------------------------------------------------------------------
 137#
 138# YAML and JSONC are intentionally absent. They are data formats, not code, so a
 139# comment associates with the surrounding data structure (key/value pair, list
 140# item, or scalar) rather than with an enclosing or following declaration. That
 141# needs a different algorithm (inline same-row association first, scalar targets,
 142# grammar-specific traversal), implemented in find_yaml_associated_structure and
 143# find_jsonc_associated_structure and dispatched from find_associated_scope.
 144# Those bespoke finders never read this table, so an entry here would be dead.
 145#
 146# Deliberately no entities yet: data members and variables, Python assignments
 147# (a lambda bound to a name included), Rust ``const``/``static``, Go ``var``/``const``,
 148# TypeScript plain consts, and macro invocations. A marker above one of them is
 149# attached to the enclosing entity, or to the file.
 150ENTITY_NODE_TYPES: dict[CommentType, set[str]] = {
[docs] 151    # @Python Scope Node Types, IMPL_PY_2, impl, [FE_PY]
 152    CommentType.python: {"function_definition", "class_definition"},
[docs] 153    # @C and C++ Scope Node Types, IMPL_C_2, impl, [FE_C_SUPPORT, FE_CPP]
 154    # A declaration, a field declaration, a class, struct, union or enum is an entity
 155    # only when its predicate holds: see ENTITY_PREDICATES.
 156    CommentType.cpp: {
 157        "function_definition",
 158        "declaration",
 159        "field_declaration",
 160        "class_specifier",
 161        "struct_specifier",
 162        "union_specifier",
 163        "enum_specifier",
 164        "namespace_definition",
 165    },
 166    CommentType.cs: {
 167        "method_declaration",
 168        "class_declaration",
 169        "property_declaration",
 170        "constructor_declaration",
 171        "destructor_declaration",
 172        "struct_declaration",
 173        "interface_declaration",
 174        "enum_declaration",
 175        "record_declaration",
 176        "operator_declaration",
 177        "conversion_operator_declaration",
 178        "indexer_declaration",
 179        "delegate_declaration",
 180        "event_declaration",
 181        "local_function_statement",
 182        "namespace_declaration",
 183        "file_scoped_namespace_declaration",
 184    },
[docs] 185    # @TypeScript Scope Node Types, IMPL_TS_2, impl, [FE_TS]
 186    # The last five are scopes only when they hold a function or a class: see
 187    # ENTITY_PREDICATES.
 188    CommentType.ts: {
 189        "function_declaration",
 190        "generator_function_declaration",
 191        "function_signature",
 192        "class_declaration",
 193        "abstract_class_declaration",
 194        "interface_declaration",
 195        "type_alias_declaration",
 196        "enum_declaration",
 197        "module",
 198        "internal_module",
 199        "method_definition",
 200        "method_signature",
 201        "abstract_method_signature",
 202        "lexical_declaration",
 203        "variable_declaration",
 204        "public_field_definition",
 205        "assignment_expression",
 206        "export_statement",
 207    },
[docs] 208    # @Rust Scope Node Types, IMPL_RUST_2, impl, [FE_RUST]
 209    CommentType.rust: {
 210        "function_item",
 211        "function_signature_item",
 212        "struct_item",
 213        "enum_item",
 214        "union_item",
 215        "impl_item",
 216        "trait_item",
 217        "mod_item",
 218        "type_item",
 219        "macro_definition",
 220    },
[docs] 221    # @Go Scope Node Types, IMPL_GO_4, impl, [FE_GO]
 222    CommentType.go: {
 223        "function_declaration",
 224        "method_declaration",
 225        "type_declaration",
 226        "type_spec",
 227        "method_elem",
 228    },
[docs] 229    # @Bash Scope Node Types, IMPL_BASH_2, impl, [FE_BASH]
 230    CommentType.bash: {"function_definition"},
 231}
 232SCOPE_NODE_TYPES = ENTITY_NODE_TYPES
 233"""The name this table had before entities were named; kept for importers."""
 234
 235# What a TypeScript declaration's value can be wrapped in without changing what it
 236# is: ``(() => {}) as Handler``, ``<Handler>(() => {})``, ``f!``, ``x satisfies T``.
 237TS_VALUE_WRAPPER_TYPES = {
 238    "parenthesized_expression",
 239    "as_expression",
 240    "satisfies_expression",
 241    "non_null_expression",
 242    "type_assertion",
 243}
 244# The values that make a declaration a scope.
 245TS_FUNCTION_VALUE_TYPES = {
 246    "arrow_function",
 247    "function_expression",
 248    "generator_function",
 249    "class",
 250}
 251
 252
 253def _unwrap_ts_value(value: TreeSitterNode | None) -> TreeSitterNode | None:
 254    """``value`` seen through the wrappers of :data:`TS_VALUE_WRAPPER_TYPES`."""
 255    while value is not None and value.type in TS_VALUE_WRAPPER_TYPES:
 256        children = value.named_children
 257        if not children:
 258            return None
 259        # ``<T>x`` names its type first, every other wrapper its expression
 260        value = children[-1] if value.type == "type_assertion" else children[0]
 261    return value
 262
 263
 264def _is_function_like(value: TreeSitterNode | None) -> bool:
 265    """True if ``value``, seen through its wrappers, is a function or a class."""
 266    value = _unwrap_ts_value(value)
 267    return value is not None and value.type in TS_FUNCTION_VALUE_TYPES
 268
 269
 270def _declares_function(node: TreeSitterNode) -> bool:
 271    """A ``const``/``let``/``var`` declaration one of whose declarators is a function,
 272    or one declared with ``declare`` (``declare const x: T``), which holds no value and
 273    is the declared thing itself. Only the ``declare``-prefixed form: a variable inside a
 274    ``declare namespace``, ``module`` or ``global`` body, or a bare ``export const x: T;``
 275    in a ``.d.ts``, is no entity."""
 276    if node.parent is not None and node.parent.type == "ambient_declaration":
 277        return True
 278    return any(
 279        _is_function_like(declarator.child_by_field_name("value"))
 280        for declarator in node.named_children
 281        if declarator.type == "variable_declarator"
 282    )
 283
 284
 285def _value_is_function(node: TreeSitterNode) -> bool:
 286    """A class field (``handler = () => {}``) or a default export of a function or class."""
 287    return _is_function_like(node.child_by_field_name("value"))
 288
 289
 290def _assigns_function(node: TreeSitterNode) -> bool:
 291    """An assignment of a function, as ``module.exports = function () {}`` is."""
 292    return _is_function_like(node.child_by_field_name("right"))
 293
 294
 295# The declarators a function declarator can be wrapped in: ``int *f();``,
 296# ``int &f();``, ``int f [[nodiscard]] ();``.
 297_CPP_DECLARATOR_WRAPPERS = {
 298    "pointer_declarator",
 299    "reference_declarator",
 300    "attributed_declarator",
 301}
 302
 303
 304def _cpp_operator_cast(node: TreeSitterNode) -> TreeSitterNode | None:
 305    """The ``operator_cast`` a conversion operator's declarator is, or ends in
 306    (``operator bool() const``, ``ns::W::operator bool() const``)."""
 307    declarator = node.child_by_field_name("declarator")
 308    while declarator is not None and declarator.type == "qualified_identifier":
 309        declarator = declarator.child_by_field_name("name")
 310    return (
 311        declarator
 312        if declarator is not None and declarator.type == "operator_cast"
 313        else None
 314    )
 315
 316
 317def _cpp_declares_function(node: TreeSitterNode) -> bool:
 318    """The declarator chain reaches a function declarator: ``int f(int);``, not the
 319    variable ``int x;`` nor the function pointer ``int (*fp)(int);``. A conversion
 320    operator (``operator bool() const;``) declares a function too."""
 321    if _cpp_operator_cast(node) is not None:
 322        return True
 323    declarator = node.child_by_field_name("declarator")
 324    for _ in range(8):
 325        if declarator is None:
 326            return False
 327        if declarator.type == "function_declarator":
 328            inner = declarator.child_by_field_name("declarator")
 329            return inner is not None and inner.type != "parenthesized_declarator"
 330        if declarator.type not in _CPP_DECLARATOR_WRAPPERS:
 331            return False
 332        declarator = declarator.child_by_field_name("declarator") or (
 333            declarator.named_children[-1] if declarator.named_children else None
 334        )
 335    return False
 336
 337
 338def in_function_body(node: TreeSitterNode) -> bool:
 339    """Whether the C or C++ ``node`` is inside a function's body (a compound statement,
 340    a lambda's included)."""
 341    parent = node.parent
 342    while parent is not None:
 343        if parent.type == "compound_statement":
 344            return True
 345        parent = parent.parent
 346    return False
 347
 348
 349def _cpp_prototype(node: TreeSitterNode) -> bool:
 350    """A function prototype outside a function body: inside one, ``SDLoc dl(Op);`` is a
 351    local variable that merely parses like a prototype."""
 352    return _cpp_declares_function(node) and not in_function_body(node)
 353
 354
 355def _cpp_has_body(node: TreeSitterNode) -> bool:
 356    """A class, struct, union or enum with a body: ``struct Foo;`` declares no entity."""
 357    return node.child_by_field_name("body") is not None
 358
 359
 360# Entity node types that are an entity only when their predicate holds, per language.
 361ENTITY_PREDICATES: dict[CommentType, dict[str, Callable[[TreeSitterNode], bool]]] = {
 362    CommentType.cpp: {
 363        "declaration": _cpp_prototype,
 364        "field_declaration": _cpp_declares_function,
 365        "class_specifier": _cpp_has_body,
 366        "struct_specifier": _cpp_has_body,
 367        "union_specifier": _cpp_has_body,
 368        "enum_specifier": _cpp_has_body,
 369    },
 370    # so that a comment does not bind to a declaration that merely precedes the one
 371    # it documents (``const flag = true;`` before a function)
 372    CommentType.ts: {
 373        "lexical_declaration": _declares_function,
 374        "variable_declaration": _declares_function,
 375        "public_field_definition": _value_is_function,
 376        "assignment_expression": _assigns_function,
 377        "export_statement": _value_is_function,
 378    },
 379}
 380SCOPE_PREDICATES = ENTITY_PREDICATES
 381"""The name this table had before entities were named; kept for importers."""
 382
 383COMMENT_TYPES = frozenset({"comment", "line_comment", "block_comment", "html_comment"})
 384"""The node kinds of a comment, in every grammar."""
 385
 386# What may stand between a leading marker and its entity, besides comments; a blank
 387# line has no node, so it is trivia in every language.
 388TRIVIA_TYPES: dict[CommentType, frozenset[str]] = {
 389    CommentType.rust: frozenset({"attribute_item", "inner_attribute_item"}),
 390    CommentType.ts: frozenset({"decorator"}),
 391    CommentType.cpp: frozenset({"attribute_declaration"}),
 392}
 393
 394# The node kinds that open an entity on a row before a comment -- an attribute, a
 395# decorator, a template header -- so a comment after one on its row is not trailing
 396# code: the item they belong to follows. An inner attribute (``#![...]``) applies to
 397# what it is inside, so it heads nothing.
 398ENTITY_HEAD_TYPES = frozenset(
 399    {
 400        "decorator",
 401        "template_parameter_list",
 402        "attribute_list",
 403        "attribute_declaration",
 404        "attribute_item",
 405    }
 406)
 407
 408# The node kinds an entity can be wrapped in, and where the wrapped node is: a field
 409# name, or ``None`` for the last named child that is no comment. The entity is the
 410# wrapped node, so a decorated or templated definition's lines exclude its decorators
 411# or its template header.
 412WRAPPER_TYPES: dict[CommentType, dict[str, str | None]] = {
 413    CommentType.cpp: {"template_declaration": None, "linkage_specification": "body"},
 414    CommentType.python: {"decorated_definition": "definition"},
 415    # an ``export_statement`` is an entity itself when it exports a function or class
 416    # value (``export default () => {}``)
 417    CommentType.ts: {
 418        "export_statement": "declaration",
 419        "ambient_declaration": None,
 420        "expression_statement": None,
 421    },
 422}
 423
 424
 425def is_entity(node: TreeSitterNode, comment_type: CommentType) -> bool:
 426    """Whether ``node`` is an entity of its language (kind and predicate)."""
 427    if node.type not in ENTITY_NODE_TYPES.get(comment_type, ()):
 428        return False
 429    predicate = ENTITY_PREDICATES.get(comment_type, {}).get(node.type)
 430    return predicate is None or predicate(node)
 431
 432
 433def is_trivia(node: TreeSitterNode, comment_type: CommentType) -> bool:
 434    """Whether ``node`` may stand between a leading marker and its entity."""
 435    return node.type in COMMENT_TYPES or node.type in TRIVIA_TYPES.get(comment_type, ())
 436
 437
 438def is_wrapper(node: TreeSitterNode, comment_type: CommentType) -> bool:
 439    """Whether ``node`` can wrap an entity (see :data:`WRAPPER_TYPES`)."""
 440    return node.type in WRAPPER_TYPES.get(comment_type, {})
 441
 442
 443def unwrap_entity(
 444    node: TreeSitterNode | None, comment_type: CommentType
 445) -> TreeSitterNode | None:
 446    """The entity ``node`` is, or wraps (``@decorator``, ``template <…>``, ``export``)."""
 447    wrappers = WRAPPER_TYPES.get(comment_type, {})
 448    for _ in range(4):
 449        if node is None:
 450            return None
 451        if is_entity(node, comment_type):
 452            return node
 453        if node.type not in wrappers:
 454            return None
 455        field = wrappers[node.type]
 456        inner = node.child_by_field_name(field) if field is not None else None
 457        if inner is None:
 458            named = [c for c in node.named_children if c.type not in COMMENT_TYPES]
 459            inner = named[-1] if named else None
 460        node = inner
 461    return None
 462
 463
 464# ---------------------------------------------------------------------------
 465# Documentation comments
 466# ---------------------------------------------------------------------------
 467
 468# The openings of a documentation comment, per language. A line opening followed by
 469# one more ``/`` (``////``) and a block opening followed by ``*`` or ``/`` (``/***``,
 470# ``/**/``) are plain comments.
 471_DOC_LINE_OPENINGS: dict[CommentType, tuple[str, ...]] = {
 472    CommentType.cpp: ("///", "//!"),
 473    CommentType.cs: ("///",),
 474    CommentType.rust: ("///", "//!"),
 475}
 476_DOC_BLOCK_OPENINGS: dict[CommentType, tuple[str, ...]] = {
 477    CommentType.cpp: ("/**", "/*!"),
 478    CommentType.cs: ("/**",),
 479    CommentType.rust: ("/**", "/*!"),
 480    CommentType.ts: ("/**",),
 481}
 482
 483
 484def is_doc_comment(
 485    node: TreeSitterNode, comment_type: CommentType, *, text: bytes | None = None
 486) -> bool:
 487    """Whether the comment ``node`` is a documentation comment of its language.
 488
 489    A Python docstring is one too, but it is a string, not a comment: the caller knows
 490    it by its capture.
 491
 492    :param text: The comment's text as the file writes it (``SourceComment.text``);
 493        ``node.text`` when not given.
 494    """
 495    written = ((node.text or b"") if text is None else text).decode("utf-8", "replace")
 496    for opening in _DOC_LINE_OPENINGS.get(comment_type, ()):
 497        if written.startswith(opening):
 498            return not (opening == "///" and written.startswith("////"))
 499    for opening in _DOC_BLOCK_OPENINGS.get(comment_type, ()):
 500        if written.startswith(opening):
 501            return opening == "/*!" or not written[len(opening) :].startswith(
 502                ("*", "/")
 503            )
 504    return False
 505
 506
 507# ---------------------------------------------------------------------------
 508# Naming: a definition node -> (kind, in-file qualified name, its components)
 509# ---------------------------------------------------------------------------
 510#
 511# Every namer builds the qualified name and its components (``semantic``) side by
 512# side, from the same walk; ``render_semantic`` spells the components back, and the
 513# truth test over the extraction fixtures holds the two together.
 514
 515
 516def _text(node: TreeSitterNode | None) -> str:
 517    if node is None:
 518        return ""
 519    return (node.text or b"").decode("utf-8", "replace")
 520
 521
 522def _is_ident_char(char: str) -> bool:
 523    return char.isalnum() or char == "_"
 524
 525
 526def _squeeze(text: str) -> str:
 527    """``text`` without whitespace, except one space between two identifier characters.
 528
 529    ``ns :: Widget`` becomes ``ns::Widget``, ``Box< T >`` becomes ``Box<T>``, and
 530    ``operator int`` keeps its space.
 531    """
 532    out: list[str] = []
 533    pending_space = False
 534    for char in text:
 535        if char.isspace():
 536            pending_space = True
 537            continue
 538        if pending_space and out and _is_ident_char(out[-1]) and _is_ident_char(char):
 539            out.append(" ")
 540        pending_space = False
 541        out.append(char)
 542    return "".join(out)
 543
 544
 545def _collapse(text: str) -> str:
 546    """``text`` with every whitespace run collapsed to one space, and trimmed."""
 547    return re.sub(r"\s+", " ", text).strip()
 548
 549
 550def _strip_template_args(text: str) -> str:
 551    """``text`` without its ``<…>`` argument lists (``operator<`` is kept)."""
 552    out: list[str] = []
 553    depth = 0
 554    for char in text:
 555        so_far = "".join(out)
 556        if char == "<" and depth == 0 and so_far.endswith(("operator", "operator<")):
 557            out.append(char)
 558        elif char == "<":
 559            depth += 1
 560        elif char == ">" and depth:
 561            depth -= 1
 562        elif depth == 0:
 563            out.append(char)
 564    return "".join(out)
 565
 566
 567def _join(separator: str, segments: list[str], own: str | None) -> str | None:
 568    if own is None:
 569        return None
 570    return separator.join([*segments, own])
 571
 572
 573def _element(kind: str, name: str) -> SemanticElement:
 574    return {"kind": kind, "name": name}
 575
 576
 577def _named(
 578    kind: EntityKind,
 579    qualified_name: str | None,
 580    semantic: Iterable[SemanticElement],
 581    name_end: int | None = None,
 582) -> EntityName:
 583    """An entity's name and its components; no components when it has no name."""
 584    if qualified_name is None:
 585        return EntityName(kind, None, None, name_end)
 586    return EntityName(kind, qualified_name, tuple(semantic), name_end)
 587
 588
 589# --- C and C++ ---------------------------------------------------------------
 590
 591_CPP_CLASS_KINDS: dict[str, EntityKind] = {
 592    "class_specifier": "class",
 593    "struct_specifier": "struct",
 594    "union_specifier": "union",
 595    "enum_specifier": "enum",
 596}
 597
 598# The kinds the namer gives a function only when a class qualifies it.
 599_CPP_MEMBER_KINDS = frozenset({"method", "constructor", "destructor"})
 600
 601
 602def _cpp_text(node: TreeSitterNode | None, source: bytes | None) -> str:
 603    """The text of ``node``, read from ``source`` when given.
 604
 605    ``source`` is the file as written: a C++ tree may be parsed from a prepared copy of
 606    it (:mod:`sphinx_codelinks.analyse.cpp_macros`), and every name is read from the
 607    file, never from that copy. Without it, the tree's own text is read.
 608    """
 609    return _cpp_bytes(node, source).decode("utf-8", "replace")
 610
 611
 612def _cpp_bytes(node: TreeSitterNode | None, source: bytes | None) -> bytes:
 613    """The bytes of ``node``, read from ``source`` when given (see :func:`_cpp_text`)."""
 614    if node is None:
 615        return b""
 616    if source is None:
 617        return node.text or b""
 618    return source[node.start_byte : node.end_byte]
 619
 620
 621@dataclass(frozen=True)
 622class _CppNaming:
 623    """How the names of a C++ tree's entities are made beyond one node."""
 624
 625    full_chain: bool = False
 626    """Keep every container, an inline namespace and a friend's classes included, as
 627    the names were made before they were left out (see :func:`cpp_renames`)."""
 628    specs: _PartialSpecs | None = None
 629    """The file's partial specialisations, which an out-of-line qualifier is spelled
 630    as (:func:`_cpp_name`)."""
 631    inline: frozenset[str] | None = None
 632    """The paths of the file's inline namespaces as the full chain spells them
 633    (``lib::v2``), which a written qualifier leaves out
 634    (:func:`_cpp_without_inline_parts`)."""
 635    macros: Callable[[int], str] | None = None
 636    """The namespaces the file's own macros open around a name ending at a byte, each
 637    followed by ``::``."""
 638
 639    def macros_at(self, name_end: int) -> str:
 640        """The namespaces the file's own macros open around a name ending at
 641        ``name_end`` (``ns::`` or empty)."""
 642        return self.macros(name_end) if self.macros is not None else ""
 643
 644    def specs_at(
 645        self, node: TreeSitterNode, source: bytes | None, name_end: int, written: str
 646    ) -> _Lookup | None:
 647        """Where the templates the name ``written`` at ``node`` (ending at
 648        ``name_end``) qualifies are looked up among the file's partial specialisations
 649        (:class:`_Lookup`), when it has any."""
 650        if self.specs is None:
 651            return None
 652        macros = self.macros_at(name_end)
 653        inline_parts = (
 654            _cpp_inline_segments(
 655                self.inline, macros, node, source, _squeeze(written).lstrip(":")
 656            )
 657            if self.inline is not None
 658            else []
 659        )
 660        return _Lookup(
 661            self.specs, _cpp_scope_of(node, source, self, macros), inline_parts
 662        )
 663
 664
 665@dataclass(frozen=True)
 666class _Lookup:
 667    """Where the templates an out-of-line qualifier names are looked up among the
 668    file's partial specialisations (:func:`_cpp_name`)."""
 669
 670    specs: _PartialSpecs
 671    """The file's partial specialisations."""
 672    scope: str
 673    """Where the name is written (:func:`_cpp_scope_of`)."""
 674    inline_parts: list[int]
 675    """The parts of the written name that name an inline namespace of the file
 676    (:func:`_cpp_inline_segments`): left out of the path, as the specialisations' own
 677    keys leave them out."""
 678
 679
 680_ALONE = _CppNaming()
 681_FULL = _CppNaming(full_chain=True)
 682
 683
 684def _cpp_templated(node: TreeSitterNode) -> bool:
 685    """Under a ``template`` with parameters (not an explicit specialisation)."""
 686    parent = node.parent
 687    while parent is not None and parent.type == "template_declaration":
 688        params = parent.child_by_field_name("parameters")
 689        if params is not None and params.named_child_count > 0:
 690            return True
 691        parent = parent.parent
 692    return False
 693
 694
 695def _cpp_template_headers(
 696    node: TreeSitterNode, source: bytes | None
 697) -> list[list[str]]:
 698    """The parameter names of each ``template <…>`` header around ``node``, outermost
 699    first (``template <typename T> template <typename U>`` is ``[T]``, ``[U]``;
 700    ``template <>`` is ``[]``; a pack ``typename... Ts`` is ``Ts``)."""
 701    headers: list[list[str]] = []
 702    parent = node.parent
 703    while parent is not None and parent.type == "template_declaration":
 704        parameters = parent.child_by_field_name("parameters")
 705        headers.append(
 706            [
 707                _cpp_parameter_name(parameter, source)
 708                for parameter in parameters.named_children
 709                if parameter.type != "comment"
 710            ]
 711            if parameters is not None
 712            else []
 713        )
 714        parent = parent.parent
 715    headers.reverse()
 716    return headers
 717
 718
 719def _cpp_parameter_name(parameter: TreeSitterNode, source: bytes | None) -> str:
 720    """The name a template parameter declares (``T`` of ``typename T = int``, ``N`` of
 721    ``int N``, ``Ts`` of ``typename... Ts``, ``TT`` of
 722    ``template <class> class TT``); empty for an unnamed one."""
 723    if parameter.type in (
 724        "type_parameter_declaration",
 725        "variadic_type_parameter_declaration",
 726    ):
 727        name = next(
 728            (c for c in parameter.named_children if c.type == "type_identifier"), None
 729        )
 730    elif parameter.type == "optional_type_parameter_declaration":
 731        name = parameter.child_by_field_name("name")
 732    elif parameter.type == "template_template_parameter_declaration":
 733        inner = next(
 734            (
 735                c
 736                for c in reversed(parameter.named_children)
 737                if c.type.endswith("type_parameter_declaration")
 738            ),
 739            None,
 740        )
 741        return _cpp_parameter_name(inner, source) if inner is not None else ""
 742    else:
 743        # a non-type parameter: the identifier its declarator ends in
 744        declarator = parameter.child_by_field_name("declarator")
 745        stack = [declarator] if declarator is not None else []
 746        name = None
 747        while stack:
 748            current = stack.pop()
 749            if current.type == "identifier":
 750                name = current
 751                break
 752            stack.extend(reversed(current.named_children))
 753    return _squeeze(_cpp_text(name, source))
 754
 755
 756def _cpp_written_arguments(part: str) -> list[str] | None:
 757    """The arguments of ``part``'s template argument list (``P<T,4>`` is ``T`` and
 758    ``4``), split at its top-level commas, a pack expansion with its ``...``
 759    (``Ts...``). ``None`` when ``part`` has no argument list."""
 760    open_at = part.find("<")
 761    if open_at < 0 or not part.endswith(">") or open_at + 1 > len(part) - 1:
 762        return None
 763    inner = part[open_at + 1 : -1]
 764    arguments: list[str] = []
 765    depth = start = 0
 766    for index, char in enumerate(inner):
 767        if char in "<([":
 768            depth += 1
 769        elif char in ">)]":
 770            depth = max(depth - 1, 0)
 771        elif char == "," and depth == 0:
 772            arguments.append(inner[start:index])
 773            start = index + 1
 774    arguments.append(inner[start:])
 775    return [argument.strip() for argument in arguments]
 776
 777
 778def _cpp_template_arguments(part: str) -> list[str] | None:
 779    """:func:`_cpp_written_arguments`, a pack expansion without its ``...`` (``Ts...``
 780    is ``Ts``): what a header's parameter names are compared with."""
 781    written = _cpp_written_arguments(part)
 782    if written is None:
 783        return None
 784    stripped = []
 785    for argument in written:
 786        while argument.endswith("..."):
 787            argument = argument[: -len("...")]
 788        stripped.append(argument)
 789    return stripped
 790
 791
 792def _cpp_words(text: str) -> list[str]:
 793    """The identifiers of ``text``, in order."""
 794    words: list[str] = []
 795    start: int | None = None
 796    for index, char in enumerate(text):
 797        if _is_ident_char(char):
 798            if start is None:
 799                start = index
 800        elif start is not None:
 801            words.append(text[start:index])
 802            start = None
 803    if start is not None:
 804        words.append(text[start:])
 805    return words
 806
 807
 808def _cpp_canonical(arguments: list[str], parameters: list[str]) -> list[str]:
 809    """``arguments`` with every name in ``parameters`` replaced by its place among the
 810    parameters named so far (``$0``, ``$1``, …): two argument lists that are the same
 811    but for their own template parameter names (``<T,4>`` under
 812    ``template <typename T>``, ``<U,4>`` under ``template <typename U>``) give the same
 813    list."""
 814    seen: list[str] = []
 815
 816    def spelled(word: str) -> str:
 817        if word not in parameters:
 818            return word
 819        if word not in seen:
 820            seen.append(word)
 821        return f"${seen.index(word)}"
 822
 823    canonical: list[str] = []
 824    for argument in arguments:
 825        out: list[str] = []
 826        start: int | None = None
 827        for index, char in enumerate(argument):
 828            if _is_ident_char(char):
 829                if start is None:
 830                    start = index
 831                continue
 832            if start is not None:
 833                out.append(spelled(argument[start:index]))
 834                start = None
 835            out.append(char)
 836        if start is not None:
 837            out.append(spelled(argument[start:]))
 838        canonical.append("".join(out))
 839    return canonical
 840
 841
 842@dataclass(frozen=True)
 843class _PartialSpec:
 844    """One partial specialisation :class:`_PartialSpecs` holds."""
 845
 846    spelled: str
 847    """Its last name part, squeezed (``P<T,4>``)."""
 848    canonical: list[str]
 849    """Its arguments up to its own parameter names (:func:`_cpp_canonical`)."""
 850    defined: bool
 851    """Whether it has a body: a forward declaration does not."""
 852
 853
 854class _PartialSpecs:
 855    """The partial specialisations of class templates a C++ file declares, by the
 856    template's qualified name (:func:`_cpp_scope_of` and its name: ``ns1::P``, so
 857    ``ns1::P<T,4>`` and ``ns2::P<U,4>`` never share one): each as it spells its last
 858    name part (``P<T,4>``), its arguments up to its own parameter names, a pack's
 859    ``...`` kept (:func:`_cpp_canonical`), and whether it has a body. An out-of-line
 860    qualifier the same but for its parameter names as exactly one spelling of those
 861    with a body is spelled as it is (:func:`_cpp_name`), so the members the
 862    specialisation defines join their in-class declarations; a forward declaration
 863    never sets the spelling, and where two spellings remain none does."""
 864
 865    def __init__(self) -> None:
 866        self.by_template: dict[str, list[_PartialSpec]] = {}
 867
 868    @classmethod
 869    def of(
 870        cls, nodes: list[TreeSitterNode], source: bytes | None, naming: _CppNaming
 871    ) -> _PartialSpecs:
 872        """The partial specialisations among ``nodes`` (:meth:`add`)."""
 873        specs = cls()
 874        for node in nodes:
 875            specs.add(node, source, naming)
 876        return specs
 877
 878    def add(
 879        self, node: TreeSitterNode, source: bytes | None, naming: _CppNaming
 880    ) -> None:
 881        """``node``, when it is a class, struct or union named with template arguments
 882        under a header with parameters (a partial specialisation), keyed as ``naming``
 883        reads where it is (the file's own macros and its inline namespaces,
 884        :func:`_cpp_scope_of`)."""
 885        if not _cpp_templated_class(node):
 886            return
 887        name = node.child_by_field_name("name")
 888        if name is None:
 889            return
 890        headers = _cpp_template_headers(node, source)
 891        if not headers or not headers[-1]:
 892            return
 893        spelled = _cpp_class_name(name, node, source)
 894        *qualifiers, part = _cpp_split(spelled)
 895        arguments = _cpp_written_arguments(part)
 896        if arguments is None:
 897            return
 898        macros = naming.macros_at(name.end_byte)
 899        inline_parts = (
 900            _cpp_inline_segments(naming.inline, macros, node, source, spelled)
 901            if naming.inline is not None
 902            else []
 903        )
 904        template = _cpp_scope_of(node, source, naming, macros)
 905        template += "".join(
 906            f"{_strip_template_args(qualifier)}::"
 907            for index, qualifier in enumerate(qualifiers)
 908            if index not in inline_parts
 909        )
 910        template += part.split("<", 1)[0]
 911        self.by_template.setdefault(template, []).append(
 912            _PartialSpec(
 913                part,
 914                _cpp_canonical(arguments, headers[-1]),
 915                node.child_by_field_name("body") is not None,
 916            )
 917        )
 918
 919    def spelling(self, scope: str, part: str, parameters: list[str]) -> str | None:
 920        """How the partial specialisations of the file spell ``part`` (a qualifier in
 921        ``scope``, :func:`_cpp_scope_of` and the qualifiers before it, under a header
 922        of ``parameters``): the one spelling of those with a body that are the same but
 923        for their parameter names, when there is exactly one."""
 924        arguments = _cpp_written_arguments(part)
 925        if arguments is None:
 926            return None
 927        wanted = _cpp_canonical(arguments, parameters)
 928        spellings = {
 929            spec.spelled
 930            for spec in self.by_template.get(scope + part.split("<", 1)[0], ())
 931            if spec.defined and spec.canonical == wanted
 932        }
 933        return next(iter(spellings)) if len(spellings) == 1 else None
 934
 935
 936def _cpp_templated_class(node: TreeSitterNode) -> bool:
 937    """``node`` is a class, struct or union under a template header: a partial
 938    specialisation when it is named with arguments under a header with parameters
 939    (:meth:`_PartialSpecs.add`)."""
 940    return (
 941        node.type in ("class_specifier", "struct_specifier", "union_specifier")
 942        and node.parent is not None
 943        and node.parent.type == "template_declaration"
 944    )
 945
 946
 947def _cpp_scope_of(
 948    node: TreeSitterNode, source: bytes | None, naming: _CppNaming, macros: str
 949) -> str:
 950    """Where the templates a name written at ``node`` qualifies are looked up:
 951    ``macros`` (the namespaces the file's own macros open around it, ``ns::`` or
 952    empty), then the namespaces and classes around it as ``naming`` names them with
 953    every inline namespace left out (:func:`_cpp_containers`), each without its
 954    arguments and followed by ``::``."""
 955    around = replace(naming, full_chain=False, specs=None)
 956    segments, _ = _cpp_containers(node, source, around)
 957    return macros + "".join(
 958        f"{_strip_template_args(part)}::"
 959        for segment in segments
 960        for part in _cpp_split(segment)
 961    )
 962
 963
 964def _cpp_name(
 965    text: str,
 966    headers: list[list[str]],
 967    *,
 968    strip_last: bool,
 969    lookup: _Lookup | None,
 970) -> str:
 971    """A name as the naming spells it: ``text`` squeezed, without a leading ``::``.
 972
 973    A qualifier's template argument list that names a parameter of the next
 974    ``template <…>`` header around the definition (``headers``, outermost first) is
 975    matched with that header; one that names none of them (an explicit specialisation,
 976    ``Outer<int>``) is kept and takes no header but an empty ``template <>``
 977    (``template <> template <class U> void Outer<int>::Inner<U>::n()`` matches
 978    ``Inner<U>`` with ``<class U>``). A matched list is spelled as a partial
 979    specialisation of the file spells its arguments when the two are the same but for
 980    their own parameter names (``lookup``, with the scope the name is written in,
 981    ``ns::`` or empty: ``template <typename U> void P<U,4>::fill()`` is ``P<T,4>::fill``
 982    when the file defines ``template <typename T> struct P<T,4> {…}``;
 983    :class:`_PartialSpecs`, :class:`_Lookup`).
 984    Otherwise it is the template's own when its arguments are exactly the header's
 985    parameter names (the primary template's member,
 986    ``template <typename T> void Box<T>::get()``), named without it (``Box::get``), and
 987    a specialisation's, kept, when they are not (``template <typename B> void
 988    W<B,4>::fill()`` is ``W<B,4>::fill``). The last part's list is stripped when
 989    ``strip_last`` (a function under a header with parameters).
 990    """
 991    parts = _cpp_split(_squeeze(text).lstrip(":"))
 992    last = len(parts) - 1
 993    remaining = iter(headers)
 994    upcoming = next(remaining, None)
 995    # where the template a qualifier names is: the scope, then the qualifiers before
 996    # it, without their arguments and without those that name an inline namespace of
 997    # the file
 998    path = lookup.scope if lookup is not None else ""
 999    spelled: list[str] = []
1000    for index, part in enumerate(parts):
1001        if index == last:
1002            spelled.append(_strip_template_args(part) if strip_last else part)
1003            break
1004        arguments = _cpp_template_arguments(part)
1005        if arguments is None:
1006            spelled.append(part)
1007        elif upcoming is None or not any(
1008            word in upcoming for argument in arguments for word in _cpp_words(argument)
1009        ):
1010            # an explicit specialisation's empty ``template <>`` is its own
1011            if upcoming == []:
1012                upcoming = next(remaining, None)
1013            spelled.append(part)
1014        else:
1015            parameters = upcoming
1016            upcoming = next(remaining, None)
1017            as_spec = (
1018                lookup.specs.spelling(path, part, parameters)
1019                if lookup is not None
1020                else None
1021            )
1022            if as_spec is not None:
1023                spelled.append(as_spec)
1024            elif parameters == arguments:
1025                spelled.append(_strip_template_args(part))
1026            else:
1027                spelled.append(part)
1028        if lookup is None or index not in lookup.inline_parts:
1029            path += f"{_strip_template_args(part)}::"
1030    return "::".join(spelled)
1031
1032
1033def _cpp_text_without_comments(node: TreeSitterNode, source: bytes | None) -> str:
1034    """The text of ``node`` with every comment inside it left out (``json.hpp`` writes
1035    ``//`` comments between a specialisation's template arguments)."""
1036    comments: list[tuple[int, int]] = []
1037    stack = [node]
1038    while stack:
1039        current = stack.pop()
1040        if current.type == "comment":
1041            comments.append((current.start_byte, current.end_byte))
1042        else:
1043            stack.extend(current.children)
1044    written = _cpp_bytes(node, source)
1045    if not comments:
1046        return written.decode("utf-8", "replace")
1047    out: list[bytes] = []
1048    position = node.start_byte
1049    for start, end in sorted(comments):
1050        out.append(written[position - node.start_byte : start - node.start_byte])
1051        out.append(b" ")
1052        position = end
1053    out.append(written[position - node.start_byte :])
1054    return b"".join(out).decode("utf-8", "replace")
1055
1056
1057def _cpp_class_name(
1058    name: TreeSitterNode, class_node: TreeSitterNode, source: bytes | None
1059) -> str:
1060    """A class's name: a specialisation's arguments kept, squeezed and without comments
1061    (``P<T,4>``, explicit or partial), as clang names it; a primary template's name has
1062    none to strip."""
1063    return _cpp_name(
1064        _cpp_text_without_comments(name, source),
1065        _cpp_template_headers(class_node, source),
1066        strip_last=False,
1067        lookup=None,
1068    )
1069
1070
1071def _cpp_class_name_in(
1072    name: TreeSitterNode,
1073    class_node: TreeSitterNode,
1074    source: bytes | None,
1075    naming: _CppNaming,
1076) -> str:
1077    """:func:`_cpp_class_name`, without the parts of a written qualifier that name an
1078    inline namespace of the file (``struct lib::v2::X {…}`` is ``lib::X``, so its
1079    members are named in it), unless ``naming`` keeps every container."""
1080    own = _cpp_class_name(name, class_node, source)
1081    if naming.inline is None or naming.full_chain:
1082        return own
1083    dropped = _cpp_inline_segments(
1084        naming.inline, naming.macros_at(name.end_byte), class_node, source, own
1085    )
1086    return _cpp_without_parts(own, dropped) if dropped else own
1087
1088
1089def _cpp_namespace_name(node: TreeSitterNode, source: bytes | None) -> str:
1090    name = node.child_by_field_name("name")
1091    if name is None:
1092        return "(anonymous namespace)"
1093    return _squeeze(_cpp_text(name, source))
1094
1095
1096def _cpp_is_inline_namespace(node: TreeSitterNode) -> bool:
1097    """The namespace ``node`` is declared ``inline`` (``inline namespace v1``)."""
1098    for child in node.children:
1099        if child.type == "namespace":
1100            return False
1101        if child.type == "inline":
1102            return True
1103    return False
1104
1105
1106def _cpp_inline_namespace_or_part(node: TreeSitterNode) -> bool:
1107    """The namespace ``node`` is inline, or holds an inline part in its nested name
1108    (``namespace a::inline b``): an ``inline`` token, as
1109    :func:`_cpp_is_inline_namespace` reads one (``namespace pipeline`` has none)."""
1110    if _cpp_is_inline_namespace(node):
1111        return True
1112    name = node.child_by_field_name("name")
1113    return name is not None and _cpp_has_inline_part(name)
1114
1115
1116def _cpp_has_inline_part(name: TreeSitterNode) -> bool:
1117    """The nested namespace specifier ``name`` holds an ``inline`` token."""
1118    if name.type != "nested_namespace_specifier":
1119        return False
1120    return any(
1121        child.type == "inline" or _cpp_has_inline_part(child) for child in name.children
1122    )
1123
1124
1125def _cpp_namespaces_around(node: TreeSitterNode, source: bytes | None) -> str:
1126    """The namespaces around ``node`` with every part kept (an inline one too),
1127    outermost first, each followed by ``::`` (``lib::v1::``)."""
1128    groups: list[list[str]] = []
1129    parent = node.parent
1130    while parent is not None:
1131        if parent.type == "namespace_definition":
1132            groups.append(_cpp_namespace_parts(parent, source, own=False, naming=_FULL))
1133        parent = parent.parent
1134    return "".join(f"{part}::" for group in reversed(groups) for part in group)
1135
1136
1137def _cpp_inline_paths_of(
1138    node: TreeSitterNode, source: bytes | None, prefix: Callable[[int], str]
1139) -> list[str]:
1140    """The paths of the inline parts of the namespace ``node`` (an inline namespace, or
1141    one with an inline part in its nested name), as the full chain spells them, after
1142    the namespaces the file's own macros open (``lib::v2``); none for an unnamed one,
1143    which no qualifier can name."""
1144    name = node.child_by_field_name("name")
1145    if name is None:
1146        return []
1147    parts = _cpp_split(_cpp_namespace_name(node, source))
1148    last = len(parts) - 1
1149    path = prefix(name.end_byte) + _cpp_namespaces_around(node, source)
1150    paths: list[str] = []
1151    for index, part in enumerate(parts):
1152        inline = part.startswith("inline ")
1153        bare = part.removeprefix("inline ")
1154        if not inline:
1155            inline = index == last and _cpp_is_inline_namespace(node)
1156        path += bare
1157        if inline:
1158            paths.append(path)
1159        path += "::"
1160    return paths
1161
1162
1163def _cpp_without_inline_parts(
1164    naming: _CppNaming,
1165    node: TreeSitterNode,
1166    source: bytes | None,
1167    own: str,
1168    name_end: int,
1169    semantic: list[SemanticElement],
1170) -> tuple[str, list[SemanticElement]]:
1171    """``own``, the name written at ``node`` (its name ending at ``name_end``), and its
1172    components ``semantic`` (which end with ``own``'s), without the parts of its
1173    qualifier that name an inline namespace of the file, as the namespaces around a
1174    node leave one out (``void lib::v2::g() {}`` is ``lib::g`` when the file declares
1175    ``inline namespace v2`` in ``lib``); unchanged with every container kept."""
1176    if naming.inline is None or naming.full_chain:
1177        return own, semantic
1178    dropped = _cpp_inline_segments(
1179        naming.inline, naming.macros_at(name_end), node, source, own
1180    )
1181    if not dropped:
1182        return own, semantic
1183    first = max(len(semantic) - len(_cpp_split(own)), 0)
1184    kept_semantic = [
1185        element
1186        for position, element in enumerate(semantic)
1187        if position - first not in dropped
1188    ]
1189    return _cpp_without_parts(own, dropped), kept_semantic
1190
1191
1192def _cpp_inline_segments(
1193    paths: frozenset[str],
1194    macros: str,
1195    node: TreeSitterNode,
1196    source: bytes | None,
1197    own: str,
1198) -> list[int]:
1199    """The parts of ``own``, the name written at ``node``, that name one of the file's
1200    inline namespaces (``paths``): indexes into its qualifier's parts, read from
1201    ``macros`` (the namespaces the file's own macros open around it) and the
1202    namespaces around ``node`` (``void lib::v2::g() {}`` is ``lib``, ``v2``, ``g``, and
1203    ``v2`` is one when the file declares ``inline namespace v2`` in ``lib``)."""
1204    *qualifier, _ = _cpp_split(own)
1205    path = macros + _cpp_namespaces_around(node, source)
1206    dropped: list[int] = []
1207    for index, part in enumerate(qualifier):
1208        path += part
1209        if path in paths:
1210            dropped.append(index)
1211        path += "::"
1212    return dropped
1213
1214
1215def _cpp_without_parts(name: str, dropped: list[int]) -> str:
1216    """``name`` without the parts at ``dropped``."""
1217    return "::".join(
1218        part for index, part in enumerate(_cpp_split(name)) if index not in dropped
1219    )
1220
1221
1222def _cpp_namespace_parts(
1223    node: TreeSitterNode, source: bytes | None, *, own: bool, naming: _CppNaming
1224) -> list[str]:
1225    """The parts of the namespace ``node``'s name (``namespace a::b`` is ``a`` and
1226    ``b``) that a name qualified by it keeps.
1227
1228    None for an inline namespace, and none for an inline part of a nested name
1229    (``a::inline b`` keeps ``a``), as clang prints a name: ``nlohmann::basic_json``,
1230    not ``nlohmann::json_abi_v3_12_0::basic_json`` (the ABI tag changes with every
1231    release). As the entity itself (``own``), its own last part is kept, without the
1232    ``inline`` (``a::inline b`` is ``a`` and ``b``). A part that reopens one of the
1233    file's inline namespaces without ``inline`` is inline too, as C++ makes it
1234    (:func:`_cpp_reopened_parts`, when ``naming`` knows them). With
1235    ``naming.full_chain``, every part is kept, an inline one without its ``inline``.
1236    """
1237    reopened = [] if naming.full_chain else _cpp_reopened_parts(node, source, naming)
1238    parts = _cpp_split(_cpp_namespace_name(node, source))
1239    last = len(parts) - 1
1240    kept: list[str] = []
1241    for index, part in enumerate(parts):
1242        inline = part.startswith("inline ") or (
1243            index == last and _cpp_is_inline_namespace(node)
1244        )
1245        if (
1246            not (inline or index in reopened)
1247            or naming.full_chain
1248            or (own and index == last)
1249        ):
1250            kept.append(part.removeprefix("inline "))
1251    return kept
1252
1253
1254def _cpp_reopened_parts(
1255    node: TreeSitterNode, source: bytes | None, naming: _CppNaming
1256) -> list[int]:
1257    """The parts of the namespace ``node``'s name that reopen one of the file's inline
1258    namespaces without ``inline`` (``namespace lib { namespace v1 {…} }`` after
1259    ``inline namespace v1`` in ``lib``): their paths, read as the file's inline paths
1260    are, are among ``naming``'s; none when it has none."""
1261    name = node.child_by_field_name("name")
1262    if naming.inline is None or name is None:
1263        return []
1264    path = naming.macros_at(name.end_byte) + _cpp_namespaces_around(node, source)
1265    reopened: list[int] = []
1266    for index, part in enumerate(_cpp_split(_cpp_namespace_name(node, source))):
1267        path += part.removeprefix("inline ")
1268        if path in naming.inline:
1269            reopened.append(index)
1270        path += "::"
1271    return reopened
1272
1273
1274def _cpp_reopens_inline(
1275    node: TreeSitterNode,
1276    source: bytes | None,
1277    prefix: Callable[[int], str],
1278    paths: set[str],
1279) -> bool:
1280    """The namespace ``node`` reopens one of the file's inline namespaces met so far
1281    without ``inline`` (a part's path is among ``paths``), which C++ makes inline too:
1282    the first declaration is the inline one."""
1283    if not paths:
1284        return False
1285    naming = _CppNaming(inline=frozenset(paths), macros=prefix)
1286    return bool(_cpp_reopened_parts(node, source, naming))
1287
1288
1289def _cpp_operator_at(text: str, index: int) -> bool:
1290    """``text`` has the word ``operator`` at ``index``."""
1291    end = index + len("operator")
1292    return text.startswith("operator", index) and (
1293        end == len(text) or not _is_ident_char(text[end])
1294    )
1295
1296
1297def _cpp_split(text: str) -> list[str]:
1298    """The components of a name as written, at its ``::``: none inside ``<…>`` or
1299    ``(…)``, and none after ``operator`` (``W::operator std::string`` is ``W`` and
1300    ``operator std::string``; ``Box<std::string>::get`` is ``Box<std::string>`` and
1301    ``get``)."""
1302    parts: list[str] = []
1303    start = depth = index = 0
1304    while index < len(text) and not (index == start and _cpp_operator_at(text, index)):
1305        char = text[index]
1306        if char in "<(":
1307            depth += 1
1308        elif char in ">)" and depth:
1309            depth -= 1
1310        elif depth == 0 and text.startswith("::", index):
1311            parts.append(text[start:index])
1312            index += 2
1313            start = index
1314            continue
1315        index += 1
1316    parts.append(text[start:])
1317    return parts
1318
1319
1320def _cpp_qualified(text: str, kind: str, *, member: bool) -> list[SemanticElement]:
1321    """The components of a name written qualified (``ns::W::f``), the last one of
1322    ``kind``. What a qualifier is, the syntax cannot tell: each is a ``scope``, except
1323    the one right before a ``member`` (a method, constructor, destructor or conversion
1324    operator), which is its ``class``."""
1325    *qualifiers, own = _cpp_split(text)
1326    elements = [_element("scope", qualifier) for qualifier in qualifiers]
1327    if member and qualifiers:
1328        elements[-1] = _element("class", qualifiers[-1])
1329    return [*elements, _element(kind, own)]
1330
1331
1332def _cpp_is_friend(node: TreeSitterNode) -> bool:
1333    """``node`` is what a ``friend`` declaration declares or defines
1334    (``friend bool operator==(const A&, const A&) {…}``, a friend template's too): a
1335    function of the innermost enclosing namespace, not of the class."""
1336    return node.parent is not None and node.parent.type == "friend_declaration"
1337
1338
1339def _cpp_containers(
1340    node: TreeSitterNode, source: bytes | None, naming: _CppNaming
1341) -> tuple[list[str], list[SemanticElement]]:
1342    """The namespaces and classes around ``node``, outermost first: their names, and
1343    their components (``namespace a::b`` is two namespaces). An inline namespace adds
1344    nothing (:func:`_cpp_namespace_parts`), and a function a ``friend`` declaration
1345    declares or defines is named in its innermost enclosing namespace, as clang names
1346    it: the classes around it add nothing."""
1347    friend = not naming.full_chain and _cpp_is_friend(node)
1348    segments: list[str] = []
1349    groups: list[list[SemanticElement]] = []
1350    parent = node.parent
1351    while parent is not None:
1352        if parent.type == "namespace_definition":
1353            parts = _cpp_namespace_parts(parent, source, own=False, naming=naming)
1354            if parts:
1355                segments.append("::".join(parts))
1356                groups.append([_element("namespace", part) for part in parts])
1357        elif not friend and parent.type in (
1358            "class_specifier",
1359            "struct_specifier",
1360            "union_specifier",
1361        ):
1362            name = parent.child_by_field_name("name")
1363            if name is not None:
1364                container = _cpp_class_name_in(name, parent, source, naming)
1365                segments.append(container)
1366                groups.append(
1367                    _cpp_qualified(
1368                        container, _CPP_CLASS_KINDS[parent.type], member=False
1369                    )
1370                )
1371        parent = parent.parent
1372    return (
1373        list(reversed(segments)),
1374        [element for group in reversed(groups) for element in group],
1375    )
1376
1377
1378def cpp_function_declarator(node: TreeSitterNode) -> TreeSitterNode | None:
1379    """The ``function_declarator`` of a function definition or declaration."""
1380    declarator = node.child_by_field_name("declarator")
1381    for _ in range(8):
1382        if declarator is None:
1383            return None
1384        if declarator.type == "function_declarator":
1385            return declarator
1386        declarator = declarator.child_by_field_name("declarator") or (
1387            declarator.named_children[-1] if declarator.named_children else None
1388        )
1389    return None
1390
1391
1392# What may stand between a class member and its class body: a member template's
1393# header, a preprocessor conditional.
1394_CPP_MEMBER_WRAPPERS = frozenset(
1395    {
1396        "template_declaration",
1397        "preproc_if",
1398        "preproc_ifdef",
1399        "preproc_elif",
1400        "preproc_elifdef",
1401        "preproc_else",
1402    }
1403)
1404
1405
1406def _cpp_in_class(node: TreeSitterNode) -> bool:
1407    """A direct member of a class body (a member template, or a member inside
1408    ``#if`` / ``#else``, counts)."""
1409    parent = node.parent
1410    while parent is not None and parent.type in _CPP_MEMBER_WRAPPERS:
1411        parent = parent.parent
1412    return parent is not None and parent.type == "field_declaration_list"
1413
1414
1415def _cpp_defined_by_macro(node: TreeSitterNode) -> bool:
1416    """A function a macro defines (``TEST(Suite, Name) {...}``): a definition with no
1417    return type, outside a class, whose declarator is a plain identifier. The function
1418    the compiler sees is the macro's expansion, which the syntax cannot name."""
1419    if node.type != "function_definition" or node.child_by_field_name("type"):
1420        return False
1421    if _cpp_in_class(node):
1422        return False
1423    declarator = cpp_function_declarator(node)
1424    inner = (
1425        declarator.child_by_field_name("declarator") if declarator is not None else None
1426    )
1427    return inner is not None and inner.type == "identifier"
1428
1429
1430def _cpp_conversion_parameters(cast: TreeSitterNode) -> TreeSitterNode | None:
1431    """A conversion operator's own parameter list: the first one under the cast's
1432    declarator, past any pointer or reference. The search starts at the cast's
1433    declarator, so a parameter list inside the target type
1434    (``operator std::function<void(int)>()``) is not it."""
1435    declarator = cast.child_by_field_name("declarator")
1436    stack = [declarator] if declarator is not None else []
1437    while stack:
1438        node = stack.pop()
1439        if node.type == "parameter_list":
1440            return node
1441        stack.extend(reversed(node.children))
1442    return None
1443
1444
1445def _cpp_conversion_target_end(cast: TreeSitterNode) -> int:
1446    """Where a conversion operator's target type ends: at its own parameter list
1447    (``operator const char*() const``), past any pointer or reference."""
1448    parameters = _cpp_conversion_parameters(cast)
1449    return parameters.start_byte if parameters is not None else cast.end_byte
1450
1451
1452def _cpp_conversion(
1453    node: TreeSitterNode,
1454    cast: TreeSitterNode,
1455    containers: tuple[list[str], list[SemanticElement]],
1456    source: bytes | None,
1457    naming: _CppNaming,
1458) -> EntityName:
1459    """A conversion operator, named ``ns::W::operator const char*``: the qualifier
1460    written before the ``operator_cast``, then ``operator`` and its target type as
1461    written, up to the parameter list. Its name ends where the target type does."""
1462    prefix, outer = containers
1463    declarator = node.child_by_field_name("declarator")
1464    written = _cpp_bytes(declarator, source)
1465    head = (
1466        written[: cast.start_byte - declarator.start_byte].decode("utf-8", "replace")
1467        if declarator is not None
1468        else ""
1469    )
1470    keyword = cast.children[0] if cast.children else cast
1471    target_end = _cpp_conversion_target_end(cast)
1472    cast_text = _cpp_bytes(cast, source)
1473    target = cast_text[
1474        keyword.end_byte - cast.start_byte : target_end - cast.start_byte
1475    ].decode("utf-8", "replace")
1476    qualifier = _cpp_name(
1477        head,
1478        _cpp_template_headers(node, source),
1479        strip_last=_cpp_templated(node),
1480        lookup=naming.specs_at(node, source, cast.start_byte, head),
1481    )
1482    own = f"{qualifier}operator {_squeeze(target)}"
1483    spelled = cast_text[: target_end - cast.start_byte].decode("utf-8", "replace")
1484    # a conversion operator is always a member: its qualifier is its class
1485    own, semantic = _cpp_without_inline_parts(
1486        naming,
1487        node,
1488        source,
1489        own,
1490        cast.start_byte,
1491        [*outer, *_cpp_qualified(own, "operator", member=True)],
1492    )
1493    return _named(
1494        "operator",
1495        "::".join([*prefix, own]),
1496        semantic,
1497        cast.start_byte + len(spelled.rstrip().encode("utf-8")),
1498    )
1499
1500
1501def _cpp_describe_in(
1502    node: TreeSitterNode, source: bytes | None, naming: _CppNaming
1503) -> EntityName | None:
1504    """Name the C or C++ entity ``node``, read from ``source`` (see :func:`_cpp_text`),
1505    its containers kept as ``naming`` says."""
1506    prefix, outer = _cpp_containers(node, source, naming)
1507    if node.type in ("function_definition", "declaration", "field_declaration"):
1508        cast = _cpp_operator_cast(node)
1509        if cast is not None:
1510            return _cpp_conversion(node, cast, (prefix, outer), source, naming)
1511        if _cpp_defined_by_macro(node):
1512            return None
1513        declarator = cpp_function_declarator(node)
1514        inner = (
1515            declarator.child_by_field_name("declarator")
1516            if declarator is not None
1517            else None
1518        )
1519        if inner is None:
1520            return None
1521        written = _cpp_text_without_comments(inner, source)
1522        own = _cpp_name(
1523            written,
1524            _cpp_template_headers(node, source),
1525            strip_last=_cpp_templated(node),
1526            lookup=naming.specs_at(node, source, inner.end_byte, written),
1527        )
1528        last = own.rsplit("::", 1)[-1]
1529        qualifier = own.rsplit("::", 1)[0] if "::" in own else None
1530        if qualifier is not None:
1531            container: str | None = _strip_template_args(qualifier.rsplit("::", 1)[-1])
1532        elif _cpp_in_class(node) and prefix:
1533            container = _strip_template_args(prefix[-1])
1534        else:
1535            container = None
1536        kind: EntityKind
1537        if last.startswith("operator"):
1538            kind = "operator"
1539        elif container is not None and last == container:
1540            kind = "constructor"
1541        elif container is not None and last.startswith("~"):
1542            kind = "destructor"
1543        elif container is not None:
1544            kind = "method"
1545        else:
1546            kind = "function"
1547        own, semantic = _cpp_without_inline_parts(
1548            naming,
1549            node,
1550            source,
1551            own,
1552            inner.end_byte,
1553            [*outer, *_cpp_qualified(own, kind, member=kind in _CPP_MEMBER_KINDS)],
1554        )
1555        return _named(kind, "::".join([*prefix, own]), semantic, inner.end_byte)
1556    if node.type in _CPP_CLASS_KINDS:
1557        class_kind = _CPP_CLASS_KINDS[node.type]
1558        name = node.child_by_field_name("name")
1559        if name is None:
1560            return EntityName(class_kind, None, None)
1561        own_name = _cpp_class_name_in(name, node, source, naming)
1562        return _named(
1563            class_kind,
1564            "::".join([*prefix, own_name]),
1565            [*outer, *_cpp_qualified(own_name, class_kind, member=False)],
1566            name.end_byte,
1567        )
1568    if node.type == "namespace_definition":
1569        own_parts = _cpp_namespace_parts(node, source, own=True, naming=naming)
1570        name = node.child_by_field_name("name")
1571        return _named(
1572            "namespace",
1573            "::".join([*prefix, "::".join(own_parts)]),
1574            [*outer, *(_element("namespace", part) for part in own_parts)],
1575            name.end_byte if name is not None else None,
1576        )
1577    return None
1578
1579
1580def _cpp_describe(node: TreeSitterNode) -> EntityName | None:
1581    return _cpp_describe_in(node, None, _ALONE)
1582
1583
1584# --- C and C++: what tells a function's overloads apart -------------------------
1585#
1586# The parameter-type rules follow codebase-memory-mcp's callable signatures
1587# (``internal/cbm/callable_sig.c``, MIT): ``(void)`` is ``()``, top-level cv-qualifiers
1588# are dropped, arrays and function types decay to pointers, and ``struct X`` is ``X``.
1589# They are re-implemented here on the syntax tree; no code is taken. Leaving out
1590# comments, attributes and the parameter's name wherever they sit is ours.
1591
1592# What a parameter's type leaves out wherever it sits: a comment, a C++ ``[[...]]``
1593# attribute, a GNU ``__attribute__((...))``.
1594_CPP_PARAMETER_NOISE = frozenset(
1595    {"comment", "attribute_declaration", "attribute_specifier"}
1596)
1597
1598# The declarators that hold their inner declarator without a ``declarator`` field: it
1599# is their first named child that is no noise.
1600_CPP_UNFIELDED_DECLARATORS = frozenset(
1601    {
1602        "attributed_declarator",
1603        "reference_declarator",
1604        "abstract_reference_declarator",
1605        "parenthesized_declarator",
1606        "abstract_parenthesized_declarator",
1607        "variadic_declarator",
1608    }
1609)
1610
1611# The cv-qualifiers a parameter's top level may carry, which do not make it another
1612# type (``const int`` is ``int``, ``int* const`` is ``int*``).
1613_CPP_TOP_LEVEL_QUALIFIERS = frozenset(
1614    {"const", "volatile", "restrict", "__restrict", "__restrict__"}
1615)
1616
1617# One edit of a parameter's text: the byte range it replaces, and its replacement.
1618_Edit = tuple[int, int, str]
1619
1620
1621def _cpp_inner_declarator(node: TreeSitterNode) -> TreeSitterNode | None:
1622    """The declarator ``node`` wraps; ``None`` at the name."""
1623    if node.type in ("identifier", "field_identifier"):
1624        return None
1625    if node.type in _CPP_UNFIELDED_DECLARATORS:
1626        return next(
1627            (c for c in node.named_children if c.type not in _CPP_PARAMETER_NOISE),
1628            None,
1629        )
1630    return node.child_by_field_name("declarator")
1631
1632
1633def _cpp_declarator_chain(parameter: TreeSitterNode) -> list[TreeSitterNode]:
1634    """A parameter's declarators, from the outermost to its name (or to the innermost
1635    one of an unnamed parameter)."""
1636    chain: list[TreeSitterNode] = []
1637    current = parameter.child_by_field_name("declarator")
1638    while current is not None and len(chain) < 16:
1639        chain.append(current)
1640        current = _cpp_inner_declarator(current)
1641    return chain
1642
1643
1644def _cpp_noise_edits(
1645    node: TreeSitterNode, source: bytes | None, edits: list[_Edit]
1646) -> None:
1647    """The edits that clean a parameter's text: every comment and attribute in
1648    ``node`` dropped, and every nested parameter list (a function pointer's) replaced
1649    by its normalised form. Walked with a stack, not by recursion, so a type nested
1650    hundreds deep (``A<A<…<int>…>>``) cannot exhaust Python's recursion limit."""
1651    stack = [node]
1652    while stack:
1653        for child in stack.pop().children:
1654            if child.type in _CPP_PARAMETER_NOISE:
1655                edits.append((child.start_byte, child.end_byte, " "))
1656            elif child.type == "parameter_list":
1657                types = _cpp_parameter_types(child, source)
1658                edits.append(
1659                    (child.start_byte, child.end_byte, f"({', '.join(types)})")
1660                )
1661            else:
1662                stack.append(child)
1663
1664
1665def _cpp_apply_edits(
1666    node: TreeSitterNode, source: bytes | None, end: int, edits: list[_Edit]
1667) -> str:
1668    """The text of ``node`` up to the byte ``end``, with ``edits`` applied (an edit
1669    inside an earlier one is dropped: the earlier one rewrote that text already)."""
1670    written = _cpp_bytes(node, source)
1671    start = node.start_byte
1672    kept = sorted(
1673        (edit for edit in edits if start <= edit[0] and edit[1] <= end),
1674        # at one position an insertion comes first, then the longest replacement
1675        key=lambda edit: (edit[0], edit[0] < edit[1], -edit[1]),
1676    )
1677    out: list[str] = []
1678    position = start
1679    for edit_start, edit_end, text in kept:
1680        if edit_start < position:
1681            continue
1682        out.append(
1683            written[position - start : edit_start - start].decode("utf-8", "replace")
1684        )
1685        out.append(text)
1686        position = edit_end
1687    out.append(written[position - start : end - start].decode("utf-8", "replace"))
1688    return "".join(out)
1689
1690
1691def _cpp_decay_edits(
1692    chain: list[TreeSitterNode], holder: int | None, named: bool, edits: list[_Edit]
1693) -> str:
1694    """The edits that decay the declarator holding a parameter's name (``holder`` in
1695    ``chain``) to a pointer when it is an array or a function: ``[...]`` is ``*``, or
1696    ``(*)`` when another array applies to it from outside (``int[3][4]`` is
1697    ``int (*)[4]``); an unnamed function type gains ``(*)``. What the name itself
1698    becomes: ``(*)`` for a named function type, else nothing."""
1699    if holder is None:
1700        return ""
1701    node = chain[holder]
1702    arrays = ("array_declarator", "abstract_array_declarator")
1703    if node.type in arrays:
1704        suffix_outside = any(outer.type in arrays for outer in chain[:holder])
1705        bracket = next((c for c in node.children if c.type == "["), None)
1706        start = bracket.start_byte if bracket is not None else node.start_byte
1707        edits.append((start, node.end_byte, "(*)" if suffix_outside else "*"))
1708    elif node.type == "function_declarator" and named:
1709        return "(*)"
1710    elif node.type == "abstract_function_declarator":
1711        edits.append((node.start_byte, node.start_byte, "(*)"))
1712    return ""
1713
1714
1715def _cpp_cv_edits(
1716    parameter: TreeSitterNode,
1717    holder: TreeSitterNode | None,
1718    source: bytes | None,
1719    edits: list[_Edit],
1720) -> bool:
1721    """The edits that drop the cv-qualifiers of the parameter itself: those of the
1722    pointer that holds its name (``int* const p``, not the ``const`` of
1723    ``int* const* p``, which is its pointee's), or its own specifiers' when no
1724    declarator holds the name (``const int x``); a reference, an array or a function
1725    there has none of its own. Whether the specifiers' were the ones."""
1726    if holder is None:
1727        qualified = parameter
1728    elif holder.type in ("pointer_declarator", "abstract_pointer_declarator"):
1729        qualified = holder
1730    else:
1731        return False
1732    for child in qualified.children:
1733        if (
1734            child.type == "type_qualifier"
1735            and _cpp_text(child, source) in _CPP_TOP_LEVEL_QUALIFIERS
1736        ):
1737            edits.append((child.start_byte, child.end_byte, ""))
1738    return holder is None
1739
1740
1741def _cpp_base_type_edits(
1742    parameter: TreeSitterNode,
1743    base: TreeSitterNode,
1744    *,
1745    reorder: bool,
1746    source: bytes | None,
1747    edits: list[_Edit],
1748) -> None:
1749    """The edits on a parameter's base type ``base``: the specifiers' ``const`` and
1750    ``volatile`` (when ``reorder``: they qualify a pointee, ``int const*``) written
1751    once, in that order, before the type; an elaborated ``struct S`` as ``S``; an
1752    integer type as the compiler spells it (``unsigned`` is ``unsigned int``)."""
1753    if reorder:
1754        specifiers = list(parameter.named_children)
1755        if base.type == "sized_type_specifier":
1756            specifiers.extend(base.named_children)
1757        present = {"const": False, "volatile": False}
1758        for qualifier in specifiers:
1759            if qualifier.type != "type_qualifier":
1760                continue
1761            written = _cpp_text(qualifier, source)
1762            if written not in present:
1763                continue
1764            present[written] = True
1765            edits.append((qualifier.start_byte, qualifier.end_byte, ""))
1766        prefix = "".join(f"{word} " for word, here in present.items() if here)
1767        if prefix:
1768            edits.append((base.start_byte, base.start_byte, prefix))
1769    if base.type in _CPP_CLASS_KINDS and base.child_by_field_name("body") is None:
1770        keyword = base.children[0] if base.children else None
1771        if keyword is not None and keyword.type in ("struct", "class", "union", "enum"):
1772            edits.append((keyword.start_byte, keyword.end_byte, ""))
1773    elif base.type == "sized_type_specifier":
1774        spelled = _cpp_sized_type(base, source)
1775        if spelled is not None:
1776            edits.append((base.start_byte, base.end_byte, spelled))
1777
1778
1779def _cpp_parameter_type(parameter: TreeSitterNode, source: bytes | None) -> str:
1780    """One parameter's type, normalised on the syntax tree so that every spelling the
1781    compiler reads as one type is one string: its comments, attributes, name and
1782    default value left out; its top-level cv-qualifiers dropped (inner ones, as in
1783    ``const int*``, kept); an array or a function type decayed to a pointer
1784    (``int[3]`` is ``int*``, ``int (int)`` is ``int (*)(int)``); ``struct X`` as
1785    ``X``; a function pointer's own parameters normalised alike; whitespace
1786    collapsed, ``&`` and ``*`` against the type (``const T&``)."""
1787    default = parameter.child_by_field_name("default_value")
1788    end = default.start_byte if default is not None else parameter.end_byte
1789    edits: list[_Edit] = []
1790    _cpp_noise_edits(parameter, source, edits)
1791
1792    chain = _cpp_declarator_chain(parameter)
1793    name = (
1794        chain[-1]
1795        if chain and chain[-1].type in ("identifier", "field_identifier")
1796        else None
1797    )
1798    # the declarator that holds the name (or the innermost of an unnamed parameter)
1799    holder = next(
1800        (
1801            index
1802            for index in range(len(chain) - 1, -1, -1)
1803            if chain[index] != name
1804            and chain[index].type
1805            not in (
1806                "attributed_declarator",
1807                "parenthesized_declarator",
1808                "abstract_parenthesized_declarator",
1809            )
1810        ),
1811        None,
1812    )
1813    name_becomes = _cpp_decay_edits(chain, holder, name is not None, edits)
1814    if name is not None:
1815        edits.append((name.start_byte, name.end_byte, name_becomes))
1816    top_level = _cpp_cv_edits(
1817        parameter, chain[holder] if holder is not None else None, source, edits
1818    )
1819    base = parameter.child_by_field_name("type")
1820    if base is not None:
1821        _cpp_base_type_edits(
1822            parameter, base, reorder=not top_level, source=source, edits=edits
1823        )
1824
1825    written = _cpp_apply_edits(parameter, source, end, edits).rstrip().rstrip("=")
1826    return _cpp_spaced_pointer_groups(
1827        _collapse(written)
1828        .replace(" &", "&")
1829        .replace(" *", "*")
1830        .replace("( ", "(")
1831        .replace(" )", ")")
1832    )
1833
1834
1835def _cpp_sized_type(node: TreeSitterNode, source: bytes | None) -> str | None:
1836    """A ``sized_type_specifier`` (``unsigned``, ``long int``, ``signed char``) spelled
1837    as the compiler spells its type: ``unsigned int``, ``long``, ``signed char``,
1838    ``long double``; ``signed`` and a redundant ``int`` dropped, ``char``'s sign kept
1839    (``char``, ``signed char`` and ``unsigned char`` are three types). ``None`` for a
1840    shape this does not know, which is then compared as written."""
1841    unsigned = short = signed = False
1842    longs = 0
1843    base: str | None = None
1844    for child in node.children:
1845        if child.type == "unsigned":
1846            unsigned = True
1847        elif child.type == "signed":
1848            signed = True
1849        elif child.type == "short":
1850            short = True
1851        elif child.type == "long":
1852            longs += 1
1853        elif child.type == "primitive_type":
1854            base = _cpp_text(child, source)
1855        elif child.type not in ("type_qualifier", "comment"):
1856            return None
1857    sign = "unsigned " if unsigned else ""
1858    if base == "char" and not short and longs == 0:
1859        return "unsigned char" if unsigned else "signed char" if signed else "char"
1860    if base == "double" and not short and longs == 1 and not unsigned and not signed:
1861        return "long double"
1862    if base not in (None, "int"):
1863        return None
1864    if short and longs == 0:
1865        return f"{sign}short"
1866    if not short and longs in (0, 1, 2):
1867        return f"{sign}{('int', 'long', 'long long')[longs]}"
1868    return None
1869
1870
1871def _cpp_spaced_pointer_groups(written: str) -> str:
1872    """``written`` with one space before each parenthesised pointer ``(*`` that follows
1873    a word, as ``int (*)(int)`` is spelled however it was written."""
1874    out: list[str] = []
1875    previous: str | None = None
1876    for index, char in enumerate(written):
1877        if (
1878            char == "("
1879            and written.startswith("*", index + 1)
1880            and previous is not None
1881            and previous not in " ("
1882        ):
1883            out.append(" ")
1884        out.append(char)
1885        previous = char
1886    return "".join(out)
1887
1888
1889def _cpp_parameter_types(parameters: TreeSitterNode, source: bytes | None) -> list[str]:
1890    """The normalised types of a parameter list's parameters (``...`` for C varargs);
1891    none for ``(void)``."""
1892    types: list[str] = []
1893    for parameter in parameters.children:
1894        if parameter.type in (
1895            "parameter_declaration",
1896            "optional_parameter_declaration",
1897            "variadic_parameter_declaration",
1898        ):
1899            types.append(_cpp_parameter_type(parameter, source))
1900        elif parameter.type in ("variadic_parameter", "..."):
1901            types.append("...")
1902    return [] if types == ["void"] else types
1903
1904
1905def cpp_signature(node: TreeSitterNode, source: bytes | None) -> str | None:
1906    """What tells a function's overloads apart: its normalised parameter types,
1907    ``, ``-joined in parentheses, then the cv- and ref-qualifiers of a member function
1908    as written (``(int) const``, ``() &&``). ``noexcept``, ``override`` and a trailing
1909    return type are left out: no overload differs by them alone."""
1910    cast = _cpp_operator_cast(node)
1911    if cast is not None:
1912        # a conversion operator's parameters and qualifiers are its cast's
1913        # (``operator bool() const``), not those of a list inside the target type
1914        parameters = _cpp_conversion_parameters(cast)
1915        declarator = parameters.parent if parameters is not None else None
1916    else:
1917        declarator = cpp_function_declarator(node)
1918        parameters = (
1919            declarator.child_by_field_name("parameters")
1920            if declarator is not None
1921            else None
1922        )
1923    if declarator is None or parameters is None:
1924        return None
1925    signature = f"({', '.join(_cpp_parameter_types(parameters, source))})"
1926    for child in declarator.children:
1927        if child.type == "type_qualifier":
1928            qualifier = _cpp_text(child, source)
1929            if qualifier not in ("const", "volatile"):
1930                continue
1931        elif child.type == "ref_qualifier":
1932            qualifier = _cpp_text(child, source)
1933        else:
1934            continue
1935        signature += f" {qualifier}"
1936    return signature
1937
1938
1939# --- C and C++: the names a whole file decides ---------------------------------
1940
1941
1942def _cpp_qualified_with_arguments(node: TreeSitterNode, source: bytes | None) -> bool:
1943    """``node`` may be named otherwise by the file's partial specialisations
1944    (:class:`_PartialSpecs`): a function whose name is qualified with a template
1945    argument list."""
1946    if node.type not in ("function_definition", "declaration", "field_declaration"):
1947        return False
1948    cast = _cpp_operator_cast(node)
1949    if cast is not None:
1950        declarator = node.child_by_field_name("declarator")
1951        if declarator is None:
1952            return False
1953        head = _cpp_bytes(declarator, source)[: cast.start_byte - declarator.start_byte]
1954        return b"<" in head
1955    declarator = cpp_function_declarator(node)
1956    inner = declarator.child_by_field_name("declarator") if declarator else None
1957    return (
1958        inner is not None
1959        and inner.type == "qualified_identifier"
1960        and b"<" in _cpp_bytes(inner, source)
1961    )
1962
1963
1964def _cpp_has_body_entity(node: TreeSitterNode) -> bool:
1965    """``node`` is a definition with a body, which a name must tell apart from every
1966    other: a function, a class, struct, union or enum with its body, a namespace."""
1967    if node.type in ("function_definition", "namespace_definition"):
1968        return True
1969    return node.type in _CPP_CLASS_KINDS and _cpp_has_body(node)
1970
1971
1972def _cpp_leaf_key(name: str) -> str:
1973    """What two names that could be the same end in: the last part of ``name`` without
1974    its argument list, every conversion operator as ``operator `` (it only picks which
1975    entities :func:`cpp_renames` compares)."""
1976    last = _cpp_split(name)[-1]
1977    if _cpp_operator_at(last, 0):
1978        return "operator " if last[len("operator") :].startswith(" ") else last
1979    return _strip_template_args(last)
1980
1981
1982def _cpp_written_leaf(node: TreeSitterNode, source: bytes | None) -> str | None:
1983    """:func:`_cpp_leaf_key` of ``node``'s name, read from its name node without naming
1984    it."""
1985    if _cpp_operator_cast(node) is not None:
1986        return "operator "
1987    if node.type in ("function_definition", "declaration", "field_declaration"):
1988        declarator = cpp_function_declarator(node)
1989        name = (
1990            declarator.child_by_field_name("declarator")
1991            if declarator is not None
1992            else None
1993        )
1994    elif node.type == "namespace_definition":
1995        name = node.child_by_field_name("name")
1996        if name is None:
1997            return "(anonymous namespace)"
1998    else:
1999        name = node.child_by_field_name("name")
2000    if name is None:
2001        return None
2002    return _cpp_leaf_key(_squeeze(_cpp_text_without_comments(name, source)))
2003
2004
2005@dataclass(frozen=True)
2006class _Seen:
2007    """One entity :func:`cpp_renames` compares."""
2008
2009    node: TreeSitterNode
2010    alone: EntityName
2011    """Its name as the node alone gives it."""
2012    named: EntityName
2013    """Its name with what the file adds to one node."""
2014    full: EntityName | None
2015    """Its name with every container kept, when one is left out."""
2016
2017
2018@dataclass
2019class _Found:
2020    """What the one walk of :func:`cpp_renames` finds."""
2021
2022    spec_nodes: list[TreeSitterNode] = field(default_factory=list)
2023    """The classes that may be partial specialisations (:func:`_cpp_templated_class`),
2024    read once the inline namespaces are known."""
2025    inline_paths: set[str] = field(default_factory=set)
2026    """The paths of its inline namespaces (:func:`_cpp_inline_paths_of`)."""
2027    inline_names: set[str] = field(default_factory=set)
2028    """Their own names (``v2``), one of which a qualifier through one holds."""
2029    left_out: list[TreeSitterNode] = field(default_factory=list)
2030    """The entities that leave a container out."""
2031    bodies: list[TreeSitterNode] = field(default_factory=list)
2032    """The other definitions."""
2033    qualified: list[TreeSitterNode] = field(default_factory=list)
2034    """The other functions qualified with arguments."""
2035    through: list[TreeSitterNode] = field(default_factory=list)
2036    """The functions named through a written qualifier
2037    (:func:`_cpp_written_qualifier`)."""
2038
2039
2040def _cpp_written_qualifier(node: TreeSitterNode, source: bytes | None) -> str | None:
2041    """The written qualified name of the function ``node`` (``lib::v2::g`` of
2042    ``void lib::v2::g() {}``, ``lib::v1::W::`` of a conversion operator
2043    ``lib::v1::W::operator int()``), when it has one."""
2044    if node.type not in ("function_definition", "declaration", "field_declaration"):
2045        return None
2046    cast = _cpp_operator_cast(node)
2047    if cast is not None:
2048        declarator = node.child_by_field_name("declarator")
2049        if declarator is None:
2050            return None
2051        head = _cpp_bytes(declarator, source)[: cast.start_byte - declarator.start_byte]
2052        return head.decode("utf-8", "replace") if b"::" in head else None
2053    declarator = cpp_function_declarator(node)
2054    inner = declarator.child_by_field_name("declarator") if declarator else None
2055    if inner is None or inner.type != "qualified_identifier":
2056        return None
2057    return _cpp_text(inner, source)
2058
2059
2060def _cpp_class_through_inline(
2061    node: TreeSitterNode, source: bytes | None, inline_names: set[str]
2062) -> bool:
2063    """The class ``node``'s head is written through a qualifier that holds the name of
2064    an inline namespace of the file met so far (``struct lib::v2::X {…}``, one of
2065    ``inline_names``)."""
2066    if node.type not in _CPP_CLASS_KINDS:
2067        return False
2068    name = node.child_by_field_name("name")
2069    # tree-sitter-cpp reads a qualified class name as this kind
2070    return (
2071        name is not None
2072        and name.type == "qualified_identifier"
2073        and any(word in inline_names for word in _cpp_words(_cpp_text(name, source)))
2074    )
2075
2076
2077def cpp_renames(
2078    root: TreeSitterNode, source: bytes | None, prefix: Callable[[int], str]
2079) -> dict[int, EntityName]:
2080    """The C++ entities under ``root`` named otherwise than the node alone names them,
2081    by the end of their name, with that name.
2082
2083    An out-of-line qualifier the same as a partial specialisation of the file but for
2084    its parameter names is spelled as that specialisation (:class:`_PartialSpecs`).
2085
2086    An inline namespace and a friend's classes are left out of a name
2087    (:func:`_cpp_containers`), and so is an inline namespace of the file a written
2088    qualifier names (``void lib::v2::g() {}`` is ``lib::g``, its full chain
2089    ``lib::v2::g``: :func:`_cpp_without_inline_parts`), except where that makes two
2090    definitions with a body share a name and parameters that the full chain kept
2091    apart: then every entity of that name and parameters that leaves a container out
2092    (its declarations too) keeps the full chain, so no marker credits another
2093    definition, and so does everything a class or namespace that keeps it holds. A
2094    declaration and one definition do not collide (a friend declared in its class and
2095    defined in its namespace stays one entity). The name compared is ``prefix`` (the
2096    namespaces the file's own macros open around a name ending at a byte) and the
2097    name, and the parameters are the normalised signature (:func:`cpp_signature`).
2098
2099    Only the entities that leave a container out, and the definitions whose name ends
2100    as one of theirs does, are named here: a file with neither friends nor inline
2101    namespaces costs one walk of its tree, which skips every function body.
2102    """
2103    # one walk: the partial specialisations, the inline namespaces, what leaves a
2104    # container out, the other definitions, the functions a specialisation may spell,
2105    # and those named through a written qualifier
2106    found = _Found()
2107    cursor = root.walk()
2108    # whether an inline namespace is around the cursor's node, per depth
2109    inside = [False]
2110    while True:
2111        node = cursor.node
2112        if node is None:  # a cursor always stands on a node
2113            return _cpp_renamed(source, prefix, found)
2114        in_inline = inside[-1]
2115        inline_here = node.type == "namespace_definition" and (
2116            _cpp_inline_namespace_or_part(node)
2117            or _cpp_reopens_inline(node, source, prefix, found.inline_paths)
2118        )
2119        # a class head written through an inline namespace leaves it out, and so does
2120        # everything the class holds
2121        through_here = not inline_here and _cpp_class_through_inline(
2122            node, source, found.inline_names
2123        )
2124        if _cpp_templated_class(node):
2125            found.spec_nodes.append(node)
2126        if inline_here:
2127            for path in _cpp_inline_paths_of(node, source, prefix):
2128                found.inline_names.add(path.rsplit("::", 1)[-1])
2129                found.inline_paths.add(path)
2130        if is_entity(node, CommentType.cpp):
2131            if _cpp_written_qualifier(node, source) is not None:
2132                found.through.append(node)
2133            if in_inline or inline_here or through_here or _cpp_is_friend(node):
2134                found.left_out.append(node)
2135            else:
2136                if _cpp_has_body_entity(node):
2137                    found.bodies.append(node)
2138                if _cpp_qualified_with_arguments(node, source):
2139                    found.qualified.append(node)
2140        # nothing a function's body holds is a friend, an inline namespace or a
2141        # partial specialisation, or names one
2142        if node.type != "function_definition" and cursor.goto_first_child():
2143            inside.append(in_inline or inline_here or through_here)
2144            continue
2145        while not cursor.goto_next_sibling():
2146            if not cursor.goto_parent():
2147                return _cpp_renamed(source, prefix, found)
2148            inside.pop()
2149
2150
2151def _cpp_leave_out_through_inline(
2152    source: bytes | None, naming: _CppNaming, found: _Found
2153) -> set[int]:
2154    """The functions of ``found.through`` whose written qualifier may name an inline
2155    namespace of the file (one of its inline names is among its words) that ``naming``
2156    names otherwise than as written and that are not in ``found.left_out``, moved there
2157    from ``found.bodies``, by node id: they leave a container out, their full chain the
2158    qualifier as written."""
2159    if naming.inline is None:
2160        return set()
2161    through = [
2162        node
2163        for node in found.through
2164        if (head := _cpp_written_qualifier(node, source)) is not None
2165        and any(word in found.inline_names for word in _cpp_words(head))
2166    ]
2167    left = {node.id for node in found.left_out}
2168    as_written = replace(naming, inline=None)
2169    moved: set[int] = set()
2170    for node in through:
2171        if node.id in left:
2172            continue
2173        named = _cpp_describe_in(node, source, naming)
2174        written = _cpp_describe_in(node, source, as_written)
2175        if (named and named.qualified_name) != (written and written.qualified_name):
2176            moved.add(node.id)
2177            found.left_out.append(node)
2178    found.bodies = [node for node in found.bodies if node.id not in moved]
2179    return moved
2180
2181
2182def _cpp_renamed(
2183    source: bytes | None, prefix: Callable[[int], str], found: _Found
2184) -> dict[int, EntityName]:
2185    """:func:`cpp_renames`, once its walk has found what it looks for
2186    (:class:`_Found`)."""
2187    base = _CppNaming(
2188        inline=frozenset(found.inline_paths) if found.inline_paths else None,
2189        macros=prefix,
2190    )
2191    specs = _PartialSpecs.of(found.spec_nodes, source, base)
2192    with_specs = replace(base, specs=specs if specs.by_template else None)
2193    full_chain = replace(with_specs, full_chain=True)
2194    moved = _cpp_leave_out_through_inline(source, with_specs, found)
2195
2196    def named_alone(node: TreeSitterNode) -> tuple[EntityName, EntityName] | None:
2197        alone = _cpp_describe_in(node, source, _ALONE)
2198        if alone is None or alone.qualified_name is None or alone.name_end is None:
2199            return None
2200        named = alone
2201        if with_specs.inline is not None or (
2202            with_specs.specs is not None and _cpp_qualified_with_arguments(node, source)
2203        ):
2204            named = _cpp_describe_in(node, source, with_specs) or alone
2205        return alone, named
2206
2207    out: dict[int, EntityName] = {}
2208    for node in found.qualified if with_specs.specs is not None else ():
2209        if node.id in moved:
2210            continue
2211        names = named_alone(node)
2212        if names is None:
2213            continue
2214        alone, named = names
2215        if named.qualified_name != alone.qualified_name and named.name_end is not None:
2216            out[named.name_end] = named
2217    # the entities that leave a container out, named both ways
2218    seen: list[_Seen] = []
2219    for node in found.left_out:
2220        names = named_alone(node)
2221        if names is None:
2222            continue
2223        alone, named = names
2224        full = _cpp_describe_in(node, source, full_chain)
2225        if full is not None and full.qualified_name == named.qualified_name:
2226            full = None
2227        if (
2228            full is None
2229            and named.qualified_name != alone.qualified_name
2230            and named.name_end is not None
2231        ):
2232            out[named.name_end] = named
2233        seen.append(_Seen(node, alone, named, full))
2234    leaves = {
2235        _cpp_leaf_key(entity.named.qualified_name)
2236        for entity in seen
2237        if entity.full is not None and entity.named.qualified_name is not None
2238    }
2239    if not leaves:
2240        return out
2241    # the other definitions that could share a name with one of them
2242    for node in found.bodies:
2243        if _cpp_written_leaf(node, source) in leaves:
2244            names = named_alone(node)
2245            if names is not None:
2246                seen.append(_Seen(node, *names, None))
2247    _cpp_keep_full_chains_where_names_collide(seen, source, prefix, out)
2248    return out
2249
2250
2251def _cpp_chain_of(name: EntityName, prefix: Callable[[int], str]) -> str:
2252    """``name`` after the namespaces the file's macros open around it: what two full
2253    chains are compared by."""
2254    at = prefix(name.name_end) if name.name_end is not None else ""
2255    return at + (name.qualified_name or "")
2256
2257
2258def _cpp_held_by(chain: str, containers: set[str]) -> bool:
2259    """The chain ``chain`` is inside one of ``containers`` (one of them is a leading
2260    part of it, ``lib::v1::Widget`` of ``lib::v1::Widget::draw``)."""
2261    parts = _cpp_split(chain)
2262    return any(
2263        "::".join(parts[: count + 1]) in containers for count in range(len(parts) - 1)
2264    )
2265
2266
2267def _cpp_keep_full_chains_where_names_collide(
2268    seen: list[_Seen],
2269    source: bytes | None,
2270    prefix: Callable[[int], str],
2271    out: dict[int, EntityName],
2272) -> None:
2273    """The names of ``seen`` into ``out`` where they differ from the names alone: each
2274    one that leaves a container out keeps its full chain where its name and
2275    parameters are those of two definitions whose full chains differ, and so does
2276    everything a class or namespace that keeps its full chain holds (its members are
2277    named in it), but a friend, which is named in its namespace."""
2278    keys = [
2279        (_cpp_chain_of(entity.named, prefix), cpp_signature(entity.node, source))
2280        for entity in seen
2281    ]
2282    chains: dict[tuple[str, str | None], set[str]] = {}
2283    for entity, key in zip(seen, keys, strict=True):
2284        if _cpp_has_body_entity(entity.node):
2285            full = entity.full or entity.named
2286            chains.setdefault(key, set()).add(_cpp_chain_of(full, prefix))
2287    collides = [
2288        entity.full is not None and len(chains.get(key, ())) > 1
2289        for entity, key in zip(seen, keys, strict=True)
2290    ]
2291    # the classes and namespaces that keep their full chain
2292    kept = {
2293        _cpp_chain_of(entity.full, prefix)
2294        for entity, collide in zip(seen, collides, strict=True)
2295        if collide
2296        and entity.full is not None
2297        and _cpp_has_body_entity(entity.node)
2298        and entity.node.type != "function_definition"
2299    }
2300    for entity, collide in zip(seen, collides, strict=True):
2301        if entity.full is None:
2302            continue
2303        # a friend is named in its namespace, never in its class
2304        keeps = collide or (
2305            bool(kept)
2306            and not _cpp_is_friend(entity.node)
2307            and _cpp_held_by(_cpp_chain_of(entity.full, prefix), kept)
2308        )
2309        name = entity.full if keeps else entity.named
2310        if (
2311            name.qualified_name != entity.alone.qualified_name
2312            and name.name_end is not None
2313        ):
2314            out[name.name_end] = name
2315
2316
2317class CppNames:
2318    """What the names of a C++ file's entities take from the whole file, beyond what
2319    one node shows: the namespaces the file's own macros open
2320    (:class:`~sphinx_codelinks.analyse.cpp_macros.MacroScopes`), and the entities named
2321    otherwise than one node names them (:func:`cpp_renames`: a name spelled as the
2322    file's partial specialisation spells it; an inline namespace or a friend's classes
2323    kept where leaving them out would give two definitions one name).
2324
2325    Made once per parse, by :func:`sphinx_codelinks.analyse.utils.parse_source`; the
2326    renames are worked out when a marker of the file first asks for a name, so a file
2327    without markers costs nothing.
2328    """
2329
2330    def __init__(
2331        self, source: bytes, root: TreeSitterNode, macros: MacroScopes | None = None
2332    ) -> None:
2333        self.source = source
2334        """The file as written: what every name is read from."""
2335        self.macros = macros if macros is not None else MacroScopes()
2336        """The namespaces the file's own macros open."""
2337        self._root = root
2338        self._renamed: dict[int, EntityName] | None = None
2339
2340    @property
2341    def renamed(self) -> dict[int, EntityName]:
2342        """The entities named otherwise, by the end of their name: the name and its
2343        parts, before the macros' namespaces."""
2344        if self._renamed is None:
2345            self._renamed = cpp_renames(self._root, self.source, self._prefix)
2346        return self._renamed
2347
2348    def _prefix(self, name_end: int) -> str:
2349        """The namespaces the file's macros open around a name ending at ``name_end``,
2350        each followed by ``::``."""
2351        return "".join(f"{n}::" for n in self.macros.namespaces_at(name_end))
2352
2353    def qualify(self, name: EntityName, start_line: int) -> EntityName:
2354        """``name``, an entity's name as its node alone gives it, as the whole file
2355        names it: renamed where the file decides so, then prefixed with the namespaces
2356        the file's macros open around the name's end, or, for an entity with no name
2357        node (an anonymous namespace), around ``start_line``, its 1-based first
2358        line."""
2359        qualified_name, semantic = name.qualified_name, name.semantic
2360        if name.name_end is not None:
2361            renamed = self.renamed.get(name.name_end)
2362            if renamed is not None:
2363                qualified_name, semantic = renamed.qualified_name, renamed.semantic
2364            namespaces = self.macros.namespaces_at(name.name_end)
2365        else:
2366            namespaces = self.macros.namespaces_at_line(start_line)
2367        if namespaces:
2368            if qualified_name is not None:
2369                qualified_name = "::".join([*namespaces, qualified_name])
2370            if semantic is not None:
2371                semantic = (
2372                    *(_element("namespace", namespace) for namespace in namespaces),
2373                    *semantic,
2374                )
2375        return replace(name, qualified_name=qualified_name, semantic=semantic)
2376
2377
2378# --- Python ------------------------------------------------------------------
2379
2380
2381def _python_semantic(definitions: list[tuple[str, str]]) -> list[SemanticElement]:
2382    """The components of a ``__qualname__``, from its definitions (node type, name),
2383    outermost first: a class, or a function -- a ``method`` directly in a class.
2384    CPython's ``<locals>`` is not one: :func:`render_semantic` writes it."""
2385    elements: list[SemanticElement] = []
2386    container: str | None = None
2387    for node_type, name in definitions:
2388        if node_type == "class_definition":
2389            kind = "class"
2390        else:
2391            kind = "method" if container == "class_definition" else "function"
2392        elements.append(_element(kind, name))
2393        container = node_type
2394    return elements
2395
2396
2397def _python_describe(node: TreeSitterNode) -> EntityName | None:
2398    """CPython's ``__qualname__``: ``Class.method``, ``outer.<locals>.inner``."""
2399    if node.type not in ("function_definition", "class_definition"):
2400        return None
2401    name = node.child_by_field_name("name")
2402    if name is None:
2403        return None
2404    segments: list[str] = []
2405    definitions = [(node.type, _text(name))]
2406    nearest: str | None = None
2407    parent = node.parent
2408    while parent is not None:
2409        if parent.type in ("class_definition", "function_definition"):
2410            nearest = nearest or parent.type
2411            parent_name = _text(parent.child_by_field_name("name"))
2412            segments.append(
2413                parent_name
2414                if parent.type == "class_definition"
2415                else f"{parent_name}.<locals>"
2416            )
2417            definitions.append((parent.type, parent_name))
2418        parent = parent.parent
2419    qualified = ".".join([*reversed(segments), _text(name)])
2420    semantic = _python_semantic(definitions[::-1])
2421    if node.type == "class_definition":
2422        return _named("class", qualified, semantic)
2423    return _named(
2424        "method" if nearest == "class_definition" else "function", qualified, semantic
2425    )
2426
2427
2428# --- Rust --------------------------------------------------------------------
2429
2430_RUST_KINDS: dict[str, EntityKind] = {
2431    "function_item": "function",
2432    "function_signature_item": "function",
2433    "struct_item": "struct",
2434    "enum_item": "enum",
2435    "union_item": "union",
2436    "trait_item": "trait",
2437    "mod_item": "module",
2438    "type_item": "type",
2439    "macro_definition": "macro",
2440}
2441
2442
2443def _rust_modules(node: TreeSitterNode) -> list[str]:
2444    segments: list[str] = []
2445    parent = node.parent
2446    while parent is not None:
2447        if parent.type == "mod_item":
2448            segments.append(_text(parent.child_by_field_name("name")))
2449        parent = parent.parent
2450    return list(reversed(segments))
2451
2452
2453def _rust_type(node: TreeSitterNode | None, modules: list[str]) -> str:
2454    """A type or trait as written: whitespace collapsed, a leading ``crate::`` dropped,
2455    and a bare name prefixed with the in-file module path."""
2456    text = _collapse(_text(node)).removeprefix("crate::")
2457    bare = node is not None and node.type in ("type_identifier", "generic_type")
2458    if bare and "::" not in text and modules:
2459        return "::".join([*modules, text])
2460    return text
2461
2462
2463# The self types rustc prints without brackets in an inherent impl's path: a path
2464# (``Widget``, ``a::Widget``, ``Widget<T>``) or a primitive.
2465_RUST_PATH_TYPES = frozenset(
2466    {"type_identifier", "generic_type", "scoped_type_identifier", "primitive_type"}
2467)
2468
2469
2470def _rust_impl_segment(impl: TreeSitterNode) -> str:
2471    """``Widget`` for an inherent impl, ``<dyn Shape>`` for an inherent impl of a type
2472    that is no path, ``<Widget as Shape>`` for a trait impl."""
2473    modules = _rust_modules(impl)
2474    self_node = impl.child_by_field_name("type")
2475    self_type = _rust_type(self_node, modules)
2476    trait = impl.child_by_field_name("trait")
2477    if trait is None:
2478        if self_node is not None and self_node.type not in _RUST_PATH_TYPES:
2479            return f"<{self_type}>"
2480        return self_type
2481    return f"<{self_type} as {_rust_type(trait, modules)}>"
2482
2483
2484def _rust_impl_parts(impl: TreeSitterNode) -> list[SemanticElement]:
2485    """An impl as components: the in-file modules it is written in, each a ``module``
2486    component of its own, then the impl, named after its self type as written and,
2487    for a trait impl, carrying the trait as the qualified name spells it.
2488
2489    The self type is written without the modules (``G`` in ``mod m``): rendering
2490    prefixes them again where the qualified name does (:func:`_rust_render`). A path
2491    from the crate root keeps its ``crate::`` where dropping it would leave a name the
2492    rendering prefixes (``crate::G`` in ``mod k`` is ``Q``, not ``k::Q``)."""
2493    modules = _rust_modules(impl)
2494    self_node = impl.child_by_field_name("type")
2495    written = _collapse(_text(self_node))
2496    name = written.removeprefix("crate::")
2497    prefixed = (
2498        bool(modules)
2499        and self_node is not None
2500        and self_node.type in ("type_identifier", "generic_type")
2501        and "::" not in name
2502    )
2503    if not prefixed and modules and written != name and _rust_resolves_in_module(name):
2504        name = written
2505    element = _element("impl", name)
2506    trait = impl.child_by_field_name("trait")
2507    if trait is not None:
2508        element["trait"] = _rust_type(trait, modules)
2509    return [*(_element("module", module) for module in modules), element]
2510
2511
2512def _rust_functions(functions: list[str], *, in_item: bool) -> list[SemanticElement]:
2513    """The functions between an item and its impl, trait or module, outermost first:
2514    the one directly in an impl or a trait (``in_item``) is a method."""
2515    return [
2516        _element("method" if in_item and index == 0 else "function", name)
2517        for index, name in enumerate(functions)
2518    ]
2519
2520
2521def _rust_describe(node: TreeSitterNode) -> EntityName | None:
2522    if node.type == "impl_item":
2523        return _named("impl", _rust_impl_segment(node), _rust_impl_parts(node))
2524    kind = _RUST_KINDS.get(node.type)
2525    name = node.child_by_field_name("name")
2526    if kind is None or name is None:
2527        return None
2528    own = _text(name)
2529    is_fn = node.type in ("function_item", "function_signature_item")
2530    functions: list[str] = []
2531    parent = node.parent
2532    while parent is not None:
2533        # a function directly in an impl or a trait is a method; one nested in a
2534        # method is a function
2535        method = is_fn and not functions
2536        own_kind: EntityKind = "method" if method else kind
2537        if parent.type == "impl_item":
2538            outer = list(reversed(functions))
2539            return _named(
2540                own_kind,
2541                "::".join([_rust_impl_segment(parent), *outer, own]),
2542                [
2543                    *_rust_impl_parts(parent),
2544                    *_rust_functions(outer, in_item=True),
2545                    _element(own_kind, own),
2546                ],
2547            )
2548        if parent.type == "trait_item":
2549            modules = _rust_modules(parent)
2550            trait_name = _text(parent.child_by_field_name("name"))
2551            outer = list(reversed(functions))
2552            return _named(
2553                own_kind,
2554                "::".join([*modules, trait_name, *outer, own]),
2555                [
2556                    *(_element("module", module) for module in modules),
2557                    _element("trait", trait_name),
2558                    *_rust_functions(outer, in_item=True),
2559                    _element(own_kind, own),
2560                ],
2561            )
2562        if parent.type == "function_item":
2563            functions.append(_text(parent.child_by_field_name("name")))
2564        if parent.type == "mod_item":
2565            break
2566        parent = parent.parent
2567    modules = _rust_modules(node)
2568    outer = list(reversed(functions))
2569    return _named(
2570        kind,
2571        "::".join([*modules, *outer, own]),
2572        [
2573            *(_element("module", module) for module in modules),
2574            *_rust_functions(outer, in_item=False),
2575            _element(kind, own),
2576        ],
2577    )
2578
2579
2580# --- C# ----------------------------------------------------------------------
2581
2582_CSHARP_KINDS: dict[str, EntityKind] = {
2583    "class_declaration": "class",
2584    "struct_declaration": "struct",
2585    "interface_declaration": "interface",
2586    "record_declaration": "record",
2587    "enum_declaration": "enum",
2588    "namespace_declaration": "namespace",
2589    "file_scoped_namespace_declaration": "namespace",
2590    "method_declaration": "method",
2591    "constructor_declaration": "constructor",
2592    "destructor_declaration": "destructor",
2593    "property_declaration": "property",
2594    "indexer_declaration": "indexer",
2595    "operator_declaration": "operator",
2596    "conversion_operator_declaration": "operator",
2597    "event_declaration": "event",
2598    "delegate_declaration": "delegate",
2599    "local_function_statement": "function",
2600}
2601_CSHARP_CONTAINERS = {
2602    "class_declaration",
2603    "struct_declaration",
2604    "interface_declaration",
2605    "record_declaration",
2606    "enum_declaration",
2607    "namespace_declaration",
2608    "method_declaration",
2609    "constructor_declaration",
2610    "local_function_statement",
2611}
2612
2613
2614def _csharp_own_name(node: TreeSitterNode) -> str | None:
2615    if node.type == "indexer_declaration":
2616        return "this[]"
2617    if node.type == "operator_declaration":
2618        symbol = next(
2619            (
2620                node.children[index + 1]
2621                for index, child in enumerate(node.children[:-1])
2622                if child.type == "operator"
2623            ),
2624            None,
2625        )
2626        return f"operator {_text(symbol)}" if symbol is not None else None
2627    if node.type == "conversion_operator_declaration":
2628        return f"operator {_squeeze(_text(node.child_by_field_name('type')))}"
2629    name = node.child_by_field_name("name")
2630    if name is None:
2631        return None
2632    own = _squeeze(_text(name))
2633    return f"~{own}" if node.type == "destructor_declaration" else own
2634
2635
2636def _csharp_elements(node: TreeSitterNode, own: str) -> list[SemanticElement]:
2637    """``node``, named ``own``, as components: a namespace's dotted name
2638    (``Acme.Gfx``) is a namespace per part."""
2639    kind = _CSHARP_KINDS[node.type]
2640    if kind == "namespace":
2641        return [_element(kind, part) for part in own.split(".")]
2642    return [_element(kind, own)]
2643
2644
2645def _csharp_describe(node: TreeSitterNode) -> EntityName | None:
2646    kind = _CSHARP_KINDS.get(node.type)
2647    if kind is None:
2648        return None
2649    segments: list[str] = []
2650    groups: list[list[SemanticElement]] = []
2651    parent = node.parent
2652    root = node
2653    while parent is not None:
2654        if parent.type in _CSHARP_CONTAINERS:
2655            container = _csharp_own_name(parent)
2656            if container is not None:
2657                segments.append(container)
2658                groups.append(_csharp_elements(parent, container))
2659        root = parent
2660        parent = parent.parent
2661    segments.reverse()
2662    groups.reverse()
2663    # a file-scoped namespace (``namespace A.B;``) is a sibling of what it holds
2664    if node.type != "file_scoped_namespace_declaration":
2665        for child in root.named_children:
2666            if child.start_byte >= node.start_byte:
2667                break
2668            if child.type == "file_scoped_namespace_declaration":
2669                namespace = _csharp_own_name(child) or ""
2670                segments.insert(0, namespace)
2671                groups.insert(0, _csharp_elements(child, namespace))
2672    own = _csharp_own_name(node)
2673    return _named(
2674        kind,
2675        _join(".", segments, own),
2676        [
2677            *(element for group in groups for element in group),
2678            *_csharp_elements(node, own or ""),
2679        ],
2680    )
2681
2682
2683# --- Go ----------------------------------------------------------------------
2684
2685
2686def _go_spec_kind(spec: TreeSitterNode) -> EntityKind:
2687    value = spec.child_by_field_name("type")
2688    if spec.type == "type_spec" and value is not None:
2689        if value.type == "struct_type":
2690            return "struct"
2691        if value.type == "interface_type":
2692            return "interface"
2693    return "type"
2694
2695
2696def _go_receiver(node: TreeSitterNode) -> str | None:
2697    """The receiver's type name, as ``go doc`` writes it (no ``*``, no type arguments)."""
2698    receiver = node.child_by_field_name("receiver")
2699    if receiver is None:
2700        return None
2701    for parameter in receiver.named_children:
2702        value = parameter.child_by_field_name("type")
2703        while value is not None and value.type in ("pointer_type", "generic_type"):
2704            value = (
2705                value.named_children[0]
2706                if value.type == "pointer_type" and value.named_children
2707                else value.child_by_field_name("type")
2708            )
2709        if value is not None:
2710            return _text(value)
2711    return None
2712
2713
2714def _go_describe(node: TreeSitterNode) -> EntityName | None:
2715    if node.type == "function_declaration":
2716        own = _text(node.child_by_field_name("name")) or None
2717        return _named("function", own, [_element("function", own or "")])
2718    if node.type == "method_declaration":
2719        own = _text(node.child_by_field_name("name"))
2720        # the receiver's type: what it is, its declaration (maybe in another file) says
2721        receiver = _go_receiver(node)
2722        if receiver:
2723            return _named(
2724                "method",
2725                f"{receiver}.{own}",
2726                [_element("type", receiver), _element("method", own)],
2727            )
2728        return _named("method", own, [_element("method", own)])
2729    if node.type == "type_declaration":
2730        specs = [
2731            child
2732            for child in node.named_children
2733            if child.type in ("type_spec", "type_alias")
2734        ]
2735        if len(specs) != 1:
2736            return EntityName("type", None, None)
2737        node = specs[0]
2738    if node.type in ("type_spec", "type_alias"):
2739        spec_kind = _go_spec_kind(node)
2740        spec_name = _text(node.child_by_field_name("name")) or None
2741        return _named(spec_kind, spec_name, [_element(spec_kind, spec_name or "")])
2742    if node.type == "method_elem":
2743        own = _text(node.child_by_field_name("name"))
2744        parent = node.parent
2745        while parent is not None and parent.type != "type_spec":
2746            parent = parent.parent
2747        owner = _text(parent.child_by_field_name("name")) if parent is not None else ""
2748        if parent is not None and owner:
2749            return _named(
2750                "method",
2751                f"{owner}.{own}",
2752                [_element(_go_spec_kind(parent), owner), _element("method", own)],
2753            )
2754        return _named("method", own, [_element("method", own)])
2755    return None
2756
2757
2758# --- TypeScript and JavaScript ----------------------------------------------
2759
2760_TS_KINDS: dict[str, EntityKind] = {
2761    "function_declaration": "function",
2762    "generator_function_declaration": "function",
2763    "function_signature": "function",
2764    "class_declaration": "class",
2765    "abstract_class_declaration": "class",
2766    "class": "class",
2767    "interface_declaration": "interface",
2768    "type_alias_declaration": "type",
2769    "enum_declaration": "enum",
2770    "module": "namespace",
2771    "internal_module": "namespace",
2772    "method_definition": "method",
2773    "method_signature": "method",
2774    "abstract_method_signature": "method",
2775    "public_field_definition": "method",
2776}
2777_TS_NAMED_CONTAINERS = {
2778    "class_declaration",
2779    "abstract_class_declaration",
2780    "class",
2781    "interface_declaration",
2782    "module",
2783    "internal_module",
2784    "function_declaration",
2785    "generator_function_declaration",
2786    "method_definition",
2787}
2788
2789
2790def _ts_value_kind(value: TreeSitterNode | None) -> EntityKind | None:
2791    value = _unwrap_ts_value(value)
2792    if value is None:
2793        return None
2794    if value.type == "class":
2795        return "class"
2796    if value.type in TS_FUNCTION_VALUE_TYPES:
2797        return "function"
2798    return None
2799
2800
2801def _ts_declarator(
2802    node: TreeSitterNode,
2803) -> tuple[TreeSitterNode | None, EntityKind | None]:
2804    """The declarator a ``const``/``let``/``var`` is named after, and its kind."""
2805    declarators = [c for c in node.named_children if c.type == "variable_declarator"]
2806    for declarator in declarators:
2807        kind = _ts_value_kind(declarator.child_by_field_name("value"))
2808        if kind is not None:
2809            return declarator, kind
2810    for declarator in declarators:
2811        annotation = declarator.child_by_field_name("type")
2812        if annotation is not None and any(
2813            child.type == "function_type" for child in annotation.named_children
2814        ):
2815            return declarator, "function"
2816    return (declarators[0] if declarators else None), None
2817
2818
2819def _ts_elements(kind: str, name: TreeSitterNode) -> list[SemanticElement]:
2820    """A declaration's name as components: a dotted namespace name (``namespace
2821    A.B``) is a namespace per part; any other name, a string one included, is one."""
2822    text = _collapse(_text(name))
2823    if name.type == "nested_identifier":
2824        return [_element(kind, part) for part in text.split(".")]
2825    return [_element(kind, text)]
2826
2827
2828def _ts_containers(node: TreeSitterNode) -> tuple[list[str], list[SemanticElement]]:
2829    segments: list[str] = []
2830    groups: list[list[SemanticElement]] = []
2831    parent = node.parent
2832    while parent is not None:
2833        if parent.type in _TS_NAMED_CONTAINERS:
2834            name = parent.child_by_field_name("name")
2835            if name is not None:
2836                segments.append(_collapse(_text(name)))
2837                groups.append(_ts_elements(_TS_KINDS[parent.type], name))
2838        elif parent.type == "variable_declarator":
2839            value_kind = _ts_value_kind(parent.child_by_field_name("value"))
2840            if value_kind is not None:
2841                declared = _text(parent.child_by_field_name("name"))
2842                segments.append(declared)
2843                groups.append([_element(value_kind, declared)])
2844        parent = parent.parent
2845    return (
2846        list(reversed(segments)),
2847        [element for group in reversed(groups) for element in group],
2848    )
2849
2850
2851def _ts_describe(node: TreeSitterNode) -> EntityName | None:
2852    prefix, outer = _ts_containers(node)
2853    if node.type in ("lexical_declaration", "variable_declaration"):
2854        declarator, kind = _ts_declarator(node)
2855        if declarator is None or kind is None:
2856            return None
2857        declared = _text(declarator.child_by_field_name("name"))
2858        return _named(
2859            kind, _join(".", prefix, declared), [*outer, _element(kind, declared)]
2860        )
2861    if node.type == "assignment_expression":
2862        kind = _ts_value_kind(node.child_by_field_name("right"))
2863        if kind is None:
2864            return None
2865        # the left-hand side is an expression, not a chain of containers: one component
2866        left = _collapse(_text(node.child_by_field_name("left")))
2867        return _named(kind, _join(".", prefix, left), [*outer, _element(kind, left)])
2868    if node.type == "export_statement":
2869        kind = _ts_value_kind(node.child_by_field_name("value"))
2870        if kind is None:
2871            return None
2872        return _named(
2873            kind, _join(".", prefix, "default"), [*outer, _element(kind, "default")]
2874        )
2875    kind = _TS_KINDS.get(node.type)
2876    if kind is None:
2877        return None
2878    name = node.child_by_field_name("name")
2879    if name is None:
2880        return EntityName(kind, None, None)
2881    return _named(
2882        kind,
2883        _join(".", prefix, _collapse(_text(name))),
2884        [*outer, *_ts_elements(kind, name)],
2885    )
2886
2887
2888# --- Bash, YAML and JSON -----------------------------------------------------
2889
2890
2891def _bash_describe(node: TreeSitterNode) -> EntityName | None:
2892    if node.type != "function_definition":
2893        return None
2894    own = _text(node.child_by_field_name("name")) or None
2895    return _named("function", own, [_element("function", own or "")])
2896
2897
2898def _data_describe(node: TreeSitterNode) -> EntityName:
2899    """A YAML or JSON entry: a key/value pair is a ``key``, anything else an ``item``."""
2900    is_pair = node.type in ("block_mapping_pair", "flow_pair", "pair")
2901    return EntityName("key" if is_pair else "item", None, None)
2902
2903
2904_DESCRIBERS: dict[CommentType, Callable[[TreeSitterNode], EntityName | None]] = {
2905    CommentType.cpp: _cpp_describe,
2906    CommentType.python: _python_describe,
2907    CommentType.rust: _rust_describe,
2908    CommentType.cs: _csharp_describe,
2909    CommentType.go: _go_describe,
2910    CommentType.ts: _ts_describe,
2911    CommentType.bash: _bash_describe,
2912    CommentType.yaml: _data_describe,
2913    CommentType.jsonc: _data_describe,
2914}
2915
2916
2917def describe_entity(
2918    node: TreeSitterNode, comment_type: CommentType, *, source: bytes | None = None
2919) -> EntityName | None:
2920    """Name the definition ``node``: its kind, its in-file qualified name, and the
2921    components of that name.
2922
2923    Pure: it reads the node and its ancestors (and, for a C# file-scoped namespace, the
2924    file's top-level siblings), never the file system.
2925
2926    :param node: A definition node, as association binds it.
2927    :param comment_type: The language of the file ``node`` was parsed from.
2928    :param source: The file as written, which a C or C++ name is read from when given
2929        (its tree may be parsed from a prepared copy); else the tree's own text.
2930    :return: The name, or ``None`` if ``node`` is no entity this function can name.
2931    """
2932    if comment_type == CommentType.cpp:
2933        return _cpp_describe_in(node, source, _ALONE)
2934    describer = _DESCRIBERS.get(comment_type)
2935    return describer(node) if describer is not None else None
2936
2937
2938# --- Rendering the components ------------------------------------------------
2939
2940
2941def _rust_is_path(text: str) -> bool:
2942    """``text`` is a type rustc writes without brackets in a path: a name, a path or a
2943    primitive, with any generic arguments (``Widget``, ``a::Widget<dyn Fn() -> u8>``,
2944    ``u32``) -- not ``dyn Shape``, ``&T``, ``[T]``, a tuple or a function type."""
2945    if text and not (_is_ident_char(text[0]) or text[0] == ":"):
2946        return False
2947    depth = 0
2948    previous = ""
2949    for char in text:
2950        if char in "<([":
2951            depth += 1
2952        elif depth and (char in ")]" or (char == ">" and previous != "-")):
2953            depth -= 1
2954        elif depth == 0 and not (_is_ident_char(char) or char == ":"):
2955            return False
2956        previous = char
2957    return True
2958
2959
2960# The types tree-sitter-rust reads as ``primitive_type``: never prefixed with a module.
2961_RUST_PRIMITIVES = frozenset(
2962    {
2963        "bool",
2964        "char",
2965        "str",
2966        "u8",
2967        "i8",
2968        "u16",
2969        "i16",
2970        "u32",
2971        "i32",
2972        "u64",
2973        "i64",
2974        "u128",
2975        "i128",
2976        "usize",
2977        "isize",
2978        "f32",
2979        "f64",
2980    }
2981)
2982
2983
2984def _rust_resolves_in_module(name: str) -> bool:
2985    """``name`` is a type written bare, which the qualified name reads as one of the
2986    modules it is written in: a name, with any generic arguments (``G``,
2987    ``Vec<T>``), not a path, a primitive or a type that is no path."""
2988    if not name or not (name[0].isalpha() or name[0] == "_") or "::" in name:
2989        return False
2990    return _rust_is_path(name) and name.split("<", 1)[0] not in _RUST_PRIMITIVES
2991
2992
2993def _rust_render(semantic: Sequence[SemanticElement]) -> str:
2994    """Rust's qualified name: the components joined with ``::``, an impl written as
2995    ``<Widget as Shape>`` for a trait impl and ``<dyn Shape>`` for a self type that is
2996    no path, and the modules before an impl folded into its self type when it is
2997    written bare (``m::G``) -- dropped otherwise, as the qualified name has no place
2998    for them (``x::P``, a path from the crate root without its ``crate::``)."""
2999    impl_at = next(
3000        (index for index, element in enumerate(semantic) if element["kind"] == "impl"),
3001        None,
3002    )
3003    if impl_at is None:
3004        return "::".join(element["name"] for element in semantic)
3005    modules = [element["name"] for element in semantic[:impl_at]]
3006    impl = semantic[impl_at]
3007    self_type = impl["name"]
3008    if modules and _rust_resolves_in_module(self_type):
3009        self_type = "::".join([*modules, self_type])
3010    else:
3011        self_type = self_type.removeprefix("crate::")
3012    if "trait" in impl:
3013        segment = f"<{self_type} as {impl['trait']}>"
3014    elif _rust_is_path(self_type):
3015        segment = self_type
3016    else:
3017        segment = f"<{self_type}>"
3018    return "::".join(
3019        [segment, *(element["name"] for element in semantic[impl_at + 1 :])]
3020    )
3021
3022
3023def _python_locals_after(semantic: Sequence[SemanticElement], index: int) -> bool:
3024    """CPython writes ``<locals>`` after the component at ``index``: a function or a
3025    method that another component follows."""
3026    return (
3027        semantic[index]["kind"] in ("function", "method") and index < len(semantic) - 1
3028    )
3029
3030
3031def render_semantic(
3032    semantic: Sequence[SemanticElement] | None, language: str
3033) -> str | None:
3034    """Spell ``semantic`` as its language writes a qualified name: the entity's
3035    ``qualified_name``.
3036
3037    C and C++ join the components with ``::``. Rust does too, writes an ``impl`` as
3038    ``<Widget as Shape>`` for a trait impl and ``<dyn Shape>`` for a self type that is
3039    no path, and folds the modules before an impl into a self type written bare
3040    (``m::G``). Python joins them with ``.``, with ``<locals>`` after a function or a
3041    method another component follows (CPython's ``__qualname__``). C#, Go,
3042    TypeScript, JavaScript and Bash join them with ``.``.
3043
3044    :param semantic: An entity's components, as :func:`describe_entity` gives them.
3045    :param language: The language of the entity's file, as ``scope.language``
3046        records it (:func:`scope_language`).
3047    :return: The qualified name; ``None`` for no components.
3048    """
3049    if semantic is None:
3050        return None
3051    if language in ("c", "cpp"):
3052        return "::".join(element["name"] for element in semantic)
3053    if language == "rust":
3054        return _rust_render(semantic)
3055    if language == "python":
3056        parts: list[str] = []
3057        for index, element in enumerate(semantic):
3058            parts.append(element["name"])
3059            if _python_locals_after(semantic, index):
3060                parts.append("<locals>")
3061        return ".".join(parts)
3062    return ".".join(element["name"] for element in semantic)
3063
3064
3065def name_chain(
3066    semantic: Sequence[SemanticElement] | None, language: str
3067) -> tuple[tuple[tuple[str, str], ...], ...] | None:
3068    """What identifies an entity in its file: the identity-bearing part of its
3069    rendering -- its components' names and other keys (a Rust impl's ``trait``), and
3070    in Python the ``<locals>`` boundaries -- without their kinds.
3071
3072    Kinds describe, names identify. A kind is a best-effort classification, for
3073    filtering and rendering, and one entity's kinds depend on how it is written: a
3074    method defined in its class is ``namespace ns``, ``class W``, ``method f``, the
3075    same method defined out of line ``scope ns``, ``class W``, ``method f``. So two
3076    entities are the same, or one is inside the other, by their name chains, never
3077    by their kinds. Where a kind does show in the name, so does the chain: CPython's
3078    ``<locals>`` after a function or a method (``A.f.<locals>.g`` is not ``A.f.g``) is
3079    a link of its own, ``(("name", "<locals>"),)``, which no Python name can be.
3080
3081    A ``qualified_name`` key is left out too: a component rebuilt from a code hop's
3082    semantic entry carries one on the entity, the rendering of this very chain, which
3083    adds no identity and would break containment (a class's chain is a prefix of its
3084    method's only without it).
3085
3086    :param semantic: An entity's components (``scope.semantic``).
3087    :param language: The entity's file's language (``scope.language``).
3088    :return: One tuple of ``(key, value)`` pairs per component, its keys sorted,
3089        ``kind`` and ``qualified_name`` left out, and a ``<locals>`` link where Python
3090        writes one; ``None`` for no components.
3091    """
3092    if semantic is None:
3093        return None
3094    chain: list[tuple[tuple[str, str], ...]] = []
3095    for index, element in enumerate(semantic):
3096        chain.append(
3097            tuple(
3098                sorted(
3099                    (key, str(value))
3100                    for key, value in element.items()
3101                    if key not in ("kind", "qualified_name")
3102                )
3103            )
3104        )
3105        if language == "python" and _python_locals_after(semantic, index):
3106            chain.append((("name", "<locals>"),))
3107    return tuple(chain)
3108
3109
3110# The suffixes of the ``ts`` comment type that are JavaScript, in lower case.
3111_JAVASCRIPT_SUFFIXES = frozenset({".js", ".jsx", ".mjs", ".cjs"})
3112
3113
3114def scope_language(comment_type: CommentType, path: PurePath) -> str:
3115    """A source file's language, by its name (``scope.language``), spelled as editors'
3116    language identifiers spell it.
3117
3118    The comment type gives the language family and the grammar; the suffix then picks
3119    ``c`` among C and C++ and ``javascript`` among the ``ts`` type's files, and the
3120    comment type alone names a file of any other suffix (a ``.py`` file in a C++
3121    project is ``cpp``). So: ``c`` for a ``.c`` file and ``cpp`` for every other C and
3122    C++ file (a ``.h`` header included: its name does not say whether it is C or C++,
3123    and it is analysed as C++; and a ``.C`` file, which compilers read as C++);
3124    ``csharp``; ``javascript`` for a ``.js``, ``.jsx``, ``.mjs`` or ``.cjs`` file, in
3125    any case, and ``typescript`` for every other file of the ``ts`` comment type,
3126    ``.tsx`` included; otherwise the comment type (``python``, ``rust``, ``go``,
3127    ``yaml``, ``jsonc``, ``bash``). A ``.c`` file is parsed with the C++ grammar too,
3128    and its components render as C++'s.
3129
3130    :param comment_type: The project's comment type.
3131    :param path: The file's path; only its suffix is read, case included for C (discovery
3132        reads it in any case, so a ``.C`` file is traced, as C++).
3133    """
3134    if comment_type == CommentType.cpp:
3135        return "c" if path.suffix == ".c" else "cpp"
3136    if comment_type == CommentType.cs:
3137        return "csharp"
3138    if comment_type == CommentType.ts:
3139        if path.suffix.lower() in _JAVASCRIPT_SUFFIXES:
3140            return "javascript"
3141        return "typescript"
3142    return comment_type.value
3143
3144
3145def code_scope(
3146    association: Association,
3147    comment_type: CommentType,
3148    language: str,
3149    names: CppNames | None = None,
3150) -> CodeScope:
3151    """The ``scope`` object of an analysis record, for ``association`` in a file of
3152    ``language`` (:func:`scope_language`).
3153
3154    ``names`` is what the whole C++ file decides about its names (:class:`CppNames`),
3155    for a C++ file the analysis parsed; the entity is named as the file names it.
3156    """
3157    node = association.node
3158    if names is None:
3159        name = describe_entity(node, comment_type)
3160    else:
3161        name = describe_entity(node, comment_type, source=names.source)
3162        if name is not None:
3163            name = names.qualify(name, node.start_point.row + 1)
3164    return {
3165        "kind": node.type,
3166        "start": node.start_point.row + 1,
3167        "end": node.end_point.row + 1,
3168        "language": language,
3169        "entity_kind": name.kind if name is not None else None,
3170        "qualified_name": name.qualified_name if name is not None else None,
3171        "semantic": (
3172            list(name.semantic)
3173            if name is not None and name.semantic is not None
3174            else None
3175        ),
3176        "mechanism": association.mechanism,
3177    }