Skip to content

Coupling gaps

coupling_gaps

Resolve structural Protocol/ABC coupling that the reference walk misses.

ast_impact walks the reference graph: it only sees a symbol when another symbol names it explicitly (import, base class, call by name). A class that implements a :class:typing.Protocol by shape — matching the method signatures without ever importing or inheriting the Protocol — and a consumer that calls such a method on an untyped receiver are both invisible to that walk. This read-only pass enumerates those shape-conforming sites so the blast-radius lower-bound gap becomes explicit.

The pass performs no file writes and re-parses nothing by hand: it consumes an already-analysed :class:~axm_ast.models.nodes.PackageInfo and reuses the tree-sitter caller primitive (:func:~axm_ast.core.callers.find_callers).

CouplingGapsResult

Bases: TypedDict

Aggregated lower-bound coupling gaps, keyed per input symbol.

Groups the three coupling passes into one batchable result so a consumer sees the full picture the reference walk alone misses. Each collection maps an input symbol to the sites the corresponding pass surfaced for it.

Attributes:

Name Type Description
reference_coupled dict[str, list[CallSite]]

The reference set — parity with find_callers for each symbol (the sites ast_impact already walks).

protocol_coupled dict[str, list[ProtocolCoupledSite]]

Structural Protocol/ABC implementors and consumers.

value_coupled dict[str, list[ValueCoupledSite]]

Sites coupled through the symbol's contract literals.

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
class CouplingGapsResult(TypedDict):
    """Aggregated lower-bound coupling gaps, keyed per input symbol.

    Groups the three coupling passes into one batchable result so a consumer
    sees the full picture the reference walk alone misses.  Each collection maps
    an input symbol to the sites the corresponding pass surfaced for it.

    Attributes:
        reference_coupled: The reference set — parity with ``find_callers`` for
            each symbol (the sites ``ast_impact`` already walks).
        protocol_coupled: Structural Protocol/ABC implementors and consumers.
        value_coupled: Sites coupled through the symbol's contract literals.
    """

    reference_coupled: dict[str, list[CallSite]]
    protocol_coupled: dict[str, list[ProtocolCoupledSite]]
    value_coupled: dict[str, list[ValueCoupledSite]]

ProtocolCoupledSite dataclass

A site structurally coupled to a Protocol/ABC without referencing it.

Attributes:

Name Type Description
file Path

Source file containing the coupled site.

line int

1-indexed line of the class definition or the call expression.

why str

Human-readable justification for surfacing the site.

confidence float

Syntactic match confidence in [0, 1] — a heuristic, since matching is by method shape only (no type inference).

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
@dataclass(frozen=True)
class ProtocolCoupledSite:
    """A site structurally coupled to a Protocol/ABC without referencing it.

    Attributes:
        file: Source file containing the coupled site.
        line: 1-indexed line of the class definition or the call expression.
        why: Human-readable justification for surfacing the site.
        confidence: Syntactic match confidence in ``[0, 1]`` — a heuristic,
            since matching is by method shape only (no type inference).
    """

    file: Path
    line: int
    why: str
    confidence: float = 1.0

ValueCoupledSite dataclass

A site coupled to a target through one of its contract literal values.

Reference-graph analysis is blind to coupling that flows through a literal value: a branch on verdict == "pass", a membership test against a literal set, or a match arm. This surfaces those literal-keyed operational sites tied to the target's declared contract literals.

Attributes:

Name Type Description
file Path

Source file containing the literal-keyed site.

line int

1-indexed line of the operational site.

why str

Human-readable justification for surfacing the site.

confidence str

Label distinguishing a high-confidence exact match (equality / match arm) from a low-confidence heuristic one (membership in a literal collection).

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
@dataclass(frozen=True)
class ValueCoupledSite:
    """A site coupled to a target through one of its contract literal values.

    Reference-graph analysis is blind to coupling that flows through a literal
    value: a branch on ``verdict == "pass"``, a membership test against a
    literal set, or a ``match`` arm.  This surfaces those literal-keyed
    operational sites tied to the target's declared contract literals.

    Attributes:
        file: Source file containing the literal-keyed site.
        line: 1-indexed line of the operational site.
        why: Human-readable justification for surfacing the site.
        confidence: Label distinguishing a high-confidence exact match
            (equality / match arm) from a low-confidence heuristic one
            (membership in a literal collection).
    """

    file: Path
    line: int
    why: str
    confidence: str = "high"

analyze_coupling_gaps(pkg, symbols)

Compose the reference walk with the two structural passes in one result.

Aggregates, per input symbol, the reference set (find_callers, so parity with the walk ast_impact performs holds), the structural Protocol/ABC coupling (find_protocol_coupled) and the contract-literal coupling (find_value_coupled). Accepts either a single symbol or a batch (list) of symbols, mirroring ast_impact's symbols batching; every input is represented as a key in each of the three collections.

Parameters:

Name Type Description Default
pkg PackageInfo

Analysed package info (tree-sitter backed; no disk read here).

required
symbols str | Sequence[str]

A single symbol name, or a batch of them to aggregate.

required

Returns:

Name Type Description
A CouplingGapsResult

class:CouplingGapsResult grouping reference_coupled,

CouplingGapsResult

protocol_coupled and value_coupled, each keyed per input symbol.

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
def analyze_coupling_gaps(
    pkg: PackageInfo,
    symbols: str | Sequence[str],
) -> CouplingGapsResult:
    """Compose the reference walk with the two structural passes in one result.

    Aggregates, per input symbol, the reference set (``find_callers``, so parity
    with the walk ``ast_impact`` performs holds), the structural Protocol/ABC
    coupling (``find_protocol_coupled``) and the contract-literal coupling
    (``find_value_coupled``).  Accepts either a single symbol or a batch (list)
    of symbols, mirroring ``ast_impact``'s ``symbols`` batching; every input is
    represented as a key in each of the three collections.

    Args:
        pkg: Analysed package info (tree-sitter backed; no disk read here).
        symbols: A single symbol name, or a batch of them to aggregate.

    Returns:
        A :class:`CouplingGapsResult` grouping ``reference_coupled``,
        ``protocol_coupled`` and ``value_coupled``, each keyed per input symbol.
    """
    targets = [symbols] if isinstance(symbols, str) else list(symbols)
    return {
        "reference_coupled": {s: find_callers(pkg, s) for s in targets},
        "protocol_coupled": {s: find_protocol_coupled(pkg, s) for s in targets},
        "value_coupled": {s: find_value_coupled(pkg, s) for s in targets},
    }

find_protocol_coupled(pkg, symbol)

Enumerate sites structurally coupled to the Protocol/ABC named symbol.

Surfaces the shape-conforming implementors and consumers that the reference-based ast_impact walk omits. When symbol does not resolve to a Protocol/ABC class in pkg, there is no structural contract to match, so an empty list is returned.

Parameters:

Name Type Description Default
pkg PackageInfo

Analysed package info (tree-sitter backed; no disk read here).

required
symbol str

Name of the Protocol/ABC to resolve structural coupling for.

required

Returns:

Type Description
list[ProtocolCoupledSite]

A list of :class:ProtocolCoupledSite, each carrying file,

list[ProtocolCoupledSite]

line and why; empty when symbol is not a Protocol/ABC member.

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
def find_protocol_coupled(pkg: PackageInfo, symbol: str) -> list[ProtocolCoupledSite]:
    """Enumerate sites structurally coupled to the Protocol/ABC named *symbol*.

    Surfaces the shape-conforming implementors and consumers that the
    reference-based ``ast_impact`` walk omits.  When *symbol* does not resolve
    to a Protocol/ABC class in *pkg*, there is no structural contract to match,
    so an empty list is returned.

    Args:
        pkg: Analysed package info (tree-sitter backed; no disk read here).
        symbol: Name of the Protocol/ABC to resolve structural coupling for.

    Returns:
        A list of :class:`ProtocolCoupledSite`, each carrying ``file``,
        ``line`` and ``why``; empty when *symbol* is not a Protocol/ABC member.
    """
    target = _find_protocol_target(pkg, symbol)
    if target is None:
        return []
    method_names = _protocol_method_names(target)
    return [
        *_structural_implementors(pkg, target, method_names),
        *_structural_consumers(pkg, target, method_names),
    ]

find_value_coupled(pkg, symbol)

Enumerate sites coupled to symbol through its contract literal values.

Derives symbol's declared contract literals (its return literals) and locates the operational sites keyed on them — equality comparisons, membership tests and match arms. Matches are scoped to the derived contract literals, so an identical literal that is not part of the contract is not reported. When symbol declares no contract literals, there is nothing to key on and an empty list is returned.

Parameters:

Name Type Description Default
pkg PackageInfo

Analysed package info (tree-sitter backed; no disk write here).

required
symbol str

Name of the target whose literal contract to resolve.

required

Returns:

Type Description
list[ValueCoupledSite]

A list of :class:ValueCoupledSite, each carrying file, line,

list[ValueCoupledSite]

why and a confidence label; empty when no contract literal is

list[ValueCoupledSite]

derivable for symbol.

Source code in packages/axm-ast/src/axm_ast/core/coupling_gaps.py
Python
def find_value_coupled(pkg: PackageInfo, symbol: str) -> list[ValueCoupledSite]:
    """Enumerate sites coupled to *symbol* through its contract literal values.

    Derives *symbol*'s declared contract literals (its return literals) and
    locates the operational sites keyed on them — equality comparisons,
    membership tests and ``match`` arms.  Matches are scoped to the derived
    contract literals, so an identical literal that is not part of the
    contract is not reported.  When *symbol* declares no contract literals,
    there is nothing to key on and an empty list is returned.

    Args:
        pkg: Analysed package info (tree-sitter backed; no disk write here).
        symbol: Name of the target whose literal contract to resolve.

    Returns:
        A list of :class:`ValueCoupledSite`, each carrying ``file``, ``line``,
        ``why`` and a ``confidence`` label; empty when no contract literal is
        derivable for *symbol*.
    """
    literals = _derive_contract_literals(pkg, symbol)
    if not literals:
        return []
    sites: list[ValueCoupledSite] = []
    for mod in pkg.modules:
        _collect_value_sites(
            parse_file(mod.path).root_node, mod.path, symbol, literals, sites
        )
    return sites