Skip to content

QIEC tooling

Shared QIEC analysis and symbol metadata for interactive tooling.

Internal module

This module is an implementation detail of the compiler and its tooling. Its __all__ is empty, so none of the names below are part of the public API, and they may change without notice.

qiec_tooling

Shared QIEC analysis and symbol metadata for interactive tooling.

The runtime compiler and the QIEC lowerer are deliberately separate pipelines. This module is the small adapter used by qvr check, the REPL, and the LSP: it lowers and validates QIEC declarations, while continuing to compile the other QVR declarations in the same source module with their compiler.

QiecToolingDiagnostic dataclass

QiecToolingDiagnostic(message: str, code: str, line: int = 0, col: int = 0)

A frontend-neutral QIEC diagnostic.

QiecBinding dataclass

QiecBinding(name: str, kind: QiecBindingKind, declaration: object, owner: Statement, scope: tuple[int, int] | None = None)

One source-level name exposed to completion and navigation.

scope class-attribute instance-attribute

scope: tuple[int, int] | None = None

The 1-based line range the binding is visible over.

None for a module-level declaration, which is visible throughout. A local binder carries the span of the region that may refer to it, so a tool can offer it at a position inside that region and nowhere else.

is_local property

is_local: bool

Whether this binding is confined to part of one declaration.

RETURNS DESCRIPTION
bool

True when the binding carries a lexical scope, which is how a caller distinguishes a name that may be offered anywhere in the module from one that may not.

qualified_name property

qualified_name: str

Return the declaration-qualified identity of this binding.

Signature members are scoped by their owning index, family, or effect. Top-level declarations already have a module-level identity, so their qualified spelling is just their authored name.

RETURNS DESCRIPTION
str

Owner.member for a signature member, and the bare name for a top-level declaration.

semantic_kind property

semantic_kind: str

The token class a highlighter should give this binding.

RETURNS DESCRIPTION
str

One of "type", "function", or "variable". The mapping is coarser than kind on purpose: an editor theme colours by role, and an index, a family, and an effect all read as types at a use site.

covers

covers(line: int) -> bool

Whether this binding is visible at a source line.

PARAMETER DESCRIPTION
line

1-based line number.

TYPE: int

RETURNS DESCRIPTION
bool

True for a module-level binding at any line, and for a local binding only inside its own span. A tool that offers a local outside its span is offering a name that does not resolve.

Source code in src/quivers/dsl/qiec_tooling.py
def covers(self, line: int) -> bool:
    """Whether this binding is visible at a source line.

    Parameters
    ----------
    line : int
        1-based line number.

    Returns
    -------
    bool
        True for a module-level binding at any line, and for a local
        binding only inside its own span. A tool that offers a local
        outside its span is offering a name that does not resolve.
    """
    if self.scope is None:
        return True
    start, end = self.scope
    return start <= line <= end

ToolingAnalysis dataclass

ToolingAnalysis(compiler: Compiler, env: dict[str, Any], non_qiec_module: Module, qiec_module: QiecModule | None, qiec_diagnostics: tuple[QiecToolingDiagnostic, ...], compile_error: CompileError | None)

The two elaboration products needed by user-facing tools.

CallSite didactic-model

CallSite(*, callee: str, owner: str, line: int, col: int, static_arguments: tuple[str, ...] = (), argument_count: int = 0)

Bases: Model

One call of a named computation in a body.

PARAMETER DESCRIPTION
callee

The computation called.

TYPE: str

owner

The name of the declaration the call sits in.

TYPE: str

line

The call's 1-based line.

TYPE: int

col

The call's 0-based column, at the callee's name.

TYPE: int

static_arguments

The call's static arguments in source form, in order.

TYPE: tuple[str, ...] DEFAULT: ()

argument_count

The number of value arguments passed.

TYPE: int DEFAULT: 0

is_qiec_statement

is_qiec_statement(statement: Statement) -> bool

Return whether statement belongs to the QIEC surface.

PARAMETER DESCRIPTION
statement

The parsed statement to classify.

TYPE: Statement

RETURNS DESCRIPTION
bool

True for a QIEC declaration. This is the split the tooling layer works from: QIEC statements go to the lowerer and everything else to the existing compiler.

Source code in src/quivers/dsl/qiec_tooling.py
def is_qiec_statement(statement: Statement) -> bool:
    """Return whether ``statement`` belongs to the QIEC surface.

    Parameters
    ----------
    statement : Statement
        The parsed statement to classify.

    Returns
    -------
    bool
        True for a QIEC declaration. This is the split the tooling layer
        works from: QIEC statements go to the lowerer and everything else
        to the existing compiler.
    """

    return isinstance(statement, QIEC_STATEMENT_TYPES)

analyze_module

analyze_module(module: Module, *, module_name: str, file_path: str | None = None) -> ToolingAnalysis

Run the QIEC lowerer and existing compiler without conflating them.

QIEC failures are returned as stable diagnostics. Existing compiler errors remain separately typed so callers can retain their established diagnostic code and presentation.

PARAMETER DESCRIPTION
module

The parsed module, carrying both QIEC and ordinary declarations.

TYPE: Module

module_name

Name the QIEC lowerer derives stable identities from. Two modules of the same name produce the same identities, so this must match what the rest of the pipeline uses.

TYPE: str

file_path

Path recorded on diagnostics, or None when the source has no file.

TYPE: str or None DEFAULT: None

RETURNS DESCRIPTION
ToolingAnalysis

The compiler, its environment, the non-QIEC projection, the lowered QIEC module when lowering succeeded, the QIEC diagnostics, and the compiler error when one occurred. Nothing raises: a tool needs whichever half succeeded even when the other failed.

Source code in src/quivers/dsl/qiec_tooling.py
def analyze_module(
    module: Module,
    *,
    module_name: str,
    file_path: str | None = None,
) -> ToolingAnalysis:
    """Run the QIEC lowerer and existing compiler without conflating them.

    QIEC failures are returned as stable diagnostics. Existing compiler errors
    remain separately typed so callers can retain their established diagnostic
    code and presentation.

    Parameters
    ----------
    module : Module
        The parsed module, carrying both QIEC and ordinary declarations.
    module_name : str
        Name the QIEC lowerer derives stable identities from. Two modules
        of the same name produce the same identities, so this must match
        what the rest of the pipeline uses.
    file_path : str or None
        Path recorded on diagnostics, or None when the source has no file.

    Returns
    -------
    ToolingAnalysis
        The compiler, its environment, the non-QIEC projection, the
        lowered QIEC module when lowering succeeded, the QIEC
        diagnostics, and the compiler error when one occurred. Nothing
        raises: a tool needs whichever half succeeded even when the other
        failed.
    """

    qiec_module: QiecModule | None = None
    qiec_diags: list[QiecToolingDiagnostic] = []
    non_qiec_module = non_qiec_projection(module)
    try:
        compiler = Compiler(
            module,
            module_name=module_name,
            file_path=file_path or "<source>",
        )
        qiec_module = compiler.qiec_module
        if qiec_module is not None and qiec_module.gap:
            qiec_diags.append(
                QiecToolingDiagnostic(
                    message=qiec_module.gap,
                    code="qiec-program-gap",
                )
            )
    except Exception as error:
        if not has_qiec_surface(module):
            raise
        qiec_diags.append(qiec_diagnostic(error))
        compiler = Compiler(non_qiec_module, lower_qiec=False)
    compile_error: CompileError | None = None
    try:
        env = compiler.compile_env()
    except CompileError as error:
        compile_error = error
        env = {}
    for name, declaration in qiec_surface_values(module).items():
        env.setdefault(name, declaration)
    return ToolingAnalysis(
        compiler=compiler,
        env=env,
        non_qiec_module=non_qiec_module,
        qiec_module=qiec_module,
        qiec_diagnostics=tuple(qiec_diags),
        compile_error=compile_error,
    )

qiec_diagnostic

qiec_diagnostic(error: Exception) -> QiecToolingDiagnostic

Normalize lowerer/kernel exceptions into the public QIEC code set.

PARAMETER DESCRIPTION
error

The exception raised by the lowerer or the kernel.

TYPE: Exception

RETURNS DESCRIPTION
QiecToolingDiagnostic

A diagnostic whose code is drawn from the published set. An exception carrying a recognised code keeps it; anything else is classified by its text, so a caller always receives a stable code rather than an implementation detail.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_diagnostic(error: Exception) -> QiecToolingDiagnostic:
    """Normalize lowerer/kernel exceptions into the public QIEC code set.

    Parameters
    ----------
    error : Exception
        The exception raised by the lowerer or the kernel.

    Returns
    -------
    QiecToolingDiagnostic
        A diagnostic whose code is drawn from the published set. An
        exception carrying a recognised code keeps it; anything else is
        classified by its text, so a caller always receives a stable code
        rather than an implementation detail.
    """

    raw_code = str(getattr(error, "code", "") or "")
    code = raw_code if raw_code in QIEC_DIAGNOSTIC_CODES else _infer_code(error)
    return QiecToolingDiagnostic(
        message=str(getattr(error, "message", "") or error),
        code=code,
        line=max(0, int(getattr(error, "line", 0) or 0)),
        col=max(
            0,
            int(getattr(error, "col", None) or getattr(error, "column", 0) or 0),
        ),
    )

qiec_bindings

qiec_bindings(module: Module) -> tuple[QiecBinding, ...]

Return top-level and signature-member names in source order.

PARAMETER DESCRIPTION
module

The parsed module to read.

TYPE: Module

RETURNS DESCRIPTION
tuple[QiecBinding, ...]

Every module-level QIEC name, and the members each declaration owns. These carry no scope, being visible throughout. Names local to a body come from qiec_local_bindings instead.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_bindings(module: Module) -> tuple[QiecBinding, ...]:
    """Return top-level and signature-member names in source order.

    Parameters
    ----------
    module : Module
        The parsed module to read.

    Returns
    -------
    tuple[QiecBinding, ...]
        Every module-level QIEC name, and the members each declaration
        owns. These carry no scope, being visible throughout. Names local
        to a body come from
        [`qiec_local_bindings`][quivers.dsl.qiec_tooling.qiec_local_bindings]
        instead.
    """

    out: list[QiecBinding] = []
    for statement in module.statements:
        if isinstance(statement, QiecIndexDecl):
            out.append(QiecBinding(statement.name, "index", statement, statement))
            out.extend(
                QiecBinding(
                    constructor.name, "index-constructor", constructor, statement
                )
                for constructor in statement.constructors
            )
        elif isinstance(statement, QiecFamilyDecl):
            out.append(QiecBinding(statement.name, "family", statement, statement))
            out.extend(
                QiecBinding(constructor.name, "constructor", constructor, statement)
                for constructor in statement.constructors
            )
        elif isinstance(statement, QiecEffectDecl):
            out.append(QiecBinding(statement.name, "effect", statement, statement))
            out.extend(
                QiecBinding(operation.name, "operation", operation, statement)
                for operation in statement.operations
            )
        elif isinstance(statement, QiecEffectInstanceDecl):
            out.append(QiecBinding(statement.name, "instance", statement, statement))
        elif isinstance(statement, QiecHandlerDecl):
            out.append(QiecBinding(statement.name, "handler", statement, statement))
        elif isinstance(statement, QiecComputationDecl):
            out.append(QiecBinding(statement.name, "computation", statement, statement))
    return tuple(out)

qiec_local_bindings

qiec_local_bindings(module: Module) -> tuple[QiecBinding, ...]

Every binder local to a declaration body, in source order.

These are the names a tool may offer only inside part of a file: let bindings, a handler clause's parameters, a scoped instance, and the names a case branch binds. Each carries the line range it is visible over, so a caller filters with QiecBinding.covers.

PARAMETER DESCRIPTION
module

The parsed module to traverse.

TYPE: Module

RETURNS DESCRIPTION
tuple[QiecBinding, ...]

The local bindings. A module with no QIEC computation or handler declarations yields nothing.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_local_bindings(module: Module) -> tuple[QiecBinding, ...]:
    """Every binder local to a declaration body, in source order.

    These are the names a tool may offer only inside part of a file:
    `let` bindings, a handler clause's parameters, a scoped instance, and
    the names a case branch binds. Each carries the line range it is
    visible over, so a caller filters with
    [`QiecBinding.covers`][quivers.dsl.qiec_tooling.QiecBinding.covers].

    Parameters
    ----------
    module : Module
        The parsed module to traverse.

    Returns
    -------
    tuple[QiecBinding, ...]
        The local bindings. A module with no QIEC computation or handler
        declarations yields nothing.
    """
    out: list[QiecBinding] = []
    for statement in module.statements:
        if isinstance(statement, QiecComputationDecl):
            out.extend(
                QiecBinding(
                    parameter.name,
                    "parameter",
                    parameter,
                    statement,
                    _computation_span(statement.body),
                )
                for parameter in statement.parameters
            )
            _walk_computation_bindings(statement.body, statement, out)
        elif isinstance(statement, QiecHandlerDecl):
            for clause in statement.clauses:
                if isinstance(clause, QiecHandlerReturnClause):
                    span = _computation_span(clause.body)
                    out.append(
                        QiecBinding(
                            clause.binder.name,
                            "parameter",
                            clause.binder,
                            statement,
                            span,
                        )
                    )
                    _walk_computation_bindings(clause.body, statement, out)
                    continue
                if not isinstance(clause, QiecHandlerOperationClause):
                    continue
                if clause.body is None:
                    continue
                span = _computation_span(clause.body)
                out.extend(
                    QiecBinding(parameter.name, "parameter", parameter, statement, span)
                    for parameter in clause.parameters
                )
                _walk_computation_bindings(clause.body, statement, out)
    return tuple(out)

qiec_binding_map

qiec_binding_map(module: Module) -> dict[str, QiecBinding]

Return unambiguous source identities for QIEC bindings.

Every signature member is available through Owner.member. A bare member spelling remains available when exactly one declaration owns that spelling and it does not collide with a top-level declaration. This keeps the convenient :type get form for a single effect while preventing a later Writer.get from silently replacing Reader.get.

PARAMETER DESCRIPTION
module

The parsed module to read.

TYPE: Module

RETURNS DESCRIPTION
dict[str, QiecBinding]

Spelling to binding. A member owned by two declarations appears only under its qualified spellings, so a lookup never silently resolves to one of two equally good answers.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_binding_map(module: Module) -> dict[str, QiecBinding]:
    """Return unambiguous source identities for QIEC bindings.

    Every signature member is available through ``Owner.member``. A bare
    member spelling remains available when exactly one declaration owns that
    spelling and it does not collide with a top-level declaration. This keeps
    the convenient ``:type get`` form for a single effect while preventing a
    later ``Writer.get`` from silently replacing ``Reader.get``.

    Parameters
    ----------
    module : Module
        The parsed module to read.

    Returns
    -------
    dict[str, QiecBinding]
        Spelling to binding. A member owned by two declarations appears
        only under its qualified spellings, so a lookup never silently
        resolves to one of two equally good answers.
    """

    bindings = qiec_bindings(module)
    out = {
        binding.name: binding
        for binding in bindings
        if binding.declaration is binding.owner
    }
    members_by_name: dict[str, list[QiecBinding]] = {}
    for binding in bindings:
        if binding.declaration is binding.owner:
            continue
        out[binding.qualified_name] = binding
        members_by_name.setdefault(binding.name, []).append(binding)
    for name, members in members_by_name.items():
        if name not in out and len(members) == 1:
            out[name] = members[0]
    return out

qiec_binding_candidates

qiec_binding_candidates(module: Module, name: str) -> tuple[QiecBinding, ...]

Return every binding matching an authored or qualified spelling.

PARAMETER DESCRIPTION
module

The parsed module to search.

TYPE: Module

name

A bare member name or a qualified Owner.member spelling.

TYPE: str

RETURNS DESCRIPTION
tuple[QiecBinding, ...]

Matching bindings. A qualified spelling yields at most one. A bare spelling yields every owner that declares it, which is what lets a caller report an ambiguity rather than resolve it arbitrarily.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_binding_candidates(module: Module, name: str) -> tuple[QiecBinding, ...]:
    """Return every binding matching an authored or qualified spelling.

    Parameters
    ----------
    module : Module
        The parsed module to search.
    name : str
        A bare member name or a qualified ``Owner.member`` spelling.

    Returns
    -------
    tuple[QiecBinding, ...]
        Matching bindings. A qualified spelling yields at most one. A
        bare spelling yields every owner that declares it, which is what
        lets a caller report an ambiguity rather than resolve it
        arbitrarily.
    """

    if "." in name:
        exact = qiec_binding_map(module).get(name)
        return (exact,) if exact is not None else ()
    return tuple(binding for binding in qiec_bindings(module) if binding.name == name)

qiec_env_kinds

qiec_env_kinds(module: Module) -> dict[str, str]

Return semantic-token classifications for QIEC names.

PARAMETER DESCRIPTION
module

The parsed module to classify.

TYPE: Module

RETURNS DESCRIPTION
dict[str, str]

Spelling to token class, for the language server's semantic token legend. Only unambiguous spellings appear, since the map it is built from omits the rest.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_env_kinds(module: Module) -> dict[str, str]:
    """Return semantic-token classifications for QIEC names.

    Parameters
    ----------
    module : Module
        The parsed module to classify.

    Returns
    -------
    dict[str, str]
        Spelling to token class, for the language server's semantic
        token legend. Only unambiguous spellings appear, since the map it
        is built from omits the rest.
    """

    return {
        name: binding.semantic_kind
        for name, binding in qiec_binding_map(module).items()
    }

qiec_surface_values

qiec_surface_values(module: Module) -> dict[str, object]

Expose authored QIEC declarations in the shared inspection environment.

PARAMETER DESCRIPTION
module

The parsed module to expose.

TYPE: Module

RETURNS DESCRIPTION
dict[str, object]

Spelling to the authored declaration node. These are source AST nodes rather than lowered or compiled objects, so inspecting a name in the REPL shows what the user wrote.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_surface_values(module: Module) -> dict[str, object]:
    """Expose authored QIEC declarations in the shared inspection environment.

    Parameters
    ----------
    module : Module
        The parsed module to expose.

    Returns
    -------
    dict[str, object]
        Spelling to the authored declaration node. These are source AST
        nodes rather than lowered or compiled objects, so inspecting a
        name in the REPL shows what the user wrote.
    """

    return {
        name: binding.declaration for name, binding in qiec_binding_map(module).items()
    }

render_operation

render_operation(operation: OperationDef) -> str

Render one operation of an effect interface as its declaration line.

PARAMETER DESCRIPTION
operation

The operation.

TYPE: OperationDef

RETURNS DESCRIPTION
str

name[binders] : Arg * Arg -> Result, the arguments joined by * as the surface writes them, or name : Result for an operation with no arguments.

Source code in src/quivers/dsl/qiec_tooling.py
def render_operation(operation: OperationDef) -> str:
    """Render one operation of an effect interface as its declaration line.

    Parameters
    ----------
    operation : OperationDef
        The operation.

    Returns
    -------
    str
        ``name[binders] : Arg * Arg -> Result``, the arguments joined by
        ``*`` as the surface writes them, or ``name : Result`` for an
        operation with no arguments.
    """
    head = f"{operation.name}{_render_telescope(operation.telescope)}"
    result = render_static(operation.result_type)
    if not operation.arguments:
        return f"{head} : {result}"
    arguments = " * ".join(
        render_static(argument.type) for argument in operation.arguments
    )
    return f"{head} : {arguments} -> {result}"

render_effect

render_effect(effect: EffectDef) -> str

Render an effect interface as its declaration.

PARAMETER DESCRIPTION
effect

The interface.

TYPE: EffectDef

RETURNS DESCRIPTION
str

The effect header line followed by one indented line per operation.

Source code in src/quivers/dsl/qiec_tooling.py
def render_effect(effect: EffectDef) -> str:
    """Render an effect interface as its declaration.

    Parameters
    ----------
    effect : EffectDef
        The interface.

    Returns
    -------
    str
        The ``effect`` header line followed by one indented line per
        operation.
    """
    lines = [f"effect {effect.ref.name}{_render_telescope(effect.telescope)}"]
    lines.extend(
        f"    {render_operation(operation)}" for operation in effect.operations
    )
    return "\n".join(lines)

prelude_effect

prelude_effect(name: str) -> EffectDef | None

The prelude interface of a name, if the prelude declares one.

PARAMETER DESCRIPTION
name

The interface's name.

TYPE: str

RETURNS DESCRIPTION
EffectDef | None

The interface, or None for a name the prelude does not declare.

Source code in src/quivers/dsl/qiec_tooling.py
def prelude_effect(name: str) -> EffectDef | None:
    """The prelude interface of a name, if the prelude declares one.

    Parameters
    ----------
    name : str
        The interface's name.

    Returns
    -------
    EffectDef | None
        The interface, or ``None`` for a name the prelude does not
        declare.
    """
    return PRELUDE_EFFECTS.get(name)

instance_effect_name

instance_effect_name(module: Module, instance: str) -> str | None

The interface a module-level instance declaration instantiates.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

instance

The instance's name.

TYPE: str

RETURNS DESCRIPTION
str | None

The interface's name, or None when the module declares no such instance.

Source code in src/quivers/dsl/qiec_tooling.py
def instance_effect_name(module: Module, instance: str) -> str | None:
    """The interface a module-level instance declaration instantiates.

    Parameters
    ----------
    module : Module
        The parsed module.
    instance : str
        The instance's name.

    Returns
    -------
    str | None
        The interface's name, or ``None`` when the module declares no
        such instance.
    """
    for statement in module.statements:
        if isinstance(statement, QiecEffectInstanceDecl) and statement.name == instance:
            return statement.effect.name
    return None

effect_operation_names

effect_operation_names(module: Module, effect: str) -> tuple[str, ...]

The operations of an interface, declared in the module or by the prelude.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

effect

The interface's name.

TYPE: str

RETURNS DESCRIPTION
tuple[str, ...]

The operation names in declaration order; empty for an unknown interface. A module's own declaration shadows the prelude's.

Source code in src/quivers/dsl/qiec_tooling.py
def effect_operation_names(module: Module, effect: str) -> tuple[str, ...]:
    """The operations of an interface, declared in the module or by the prelude.

    Parameters
    ----------
    module : Module
        The parsed module.
    effect : str
        The interface's name.

    Returns
    -------
    tuple[str, ...]
        The operation names in declaration order; empty for an unknown
        interface. A module's own declaration shadows the prelude's.
    """
    for statement in module.statements:
        if isinstance(statement, QiecEffectDecl) and statement.name == effect:
            return tuple(operation.name for operation in statement.operations)
    prelude = PRELUDE_EFFECTS.get(effect)
    if prelude is None:
        return ()
    return tuple(operation.name for operation in prelude.operations)

call_sites

call_sites(module: Module) -> tuple[CallSite, ...]

Every call of a named computation in a module.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

RETURNS DESCRIPTION
tuple[CallSite, ...]

The calls in computation bodies, handler clause bodies, and program bodies, in source order. A recursive call is a call whose callee is its owner.

Source code in src/quivers/dsl/qiec_tooling.py
def call_sites(module: Module) -> tuple[CallSite, ...]:
    """Every call of a named computation in a module.

    Parameters
    ----------
    module : Module
        The parsed module.

    Returns
    -------
    tuple[CallSite, ...]
        The calls in computation bodies, handler clause bodies, and
        program bodies, in source order. A recursive call is a call whose
        callee is its owner.
    """
    out: list[CallSite] = []
    for statement in module.statements:
        if isinstance(statement, QiecComputationDecl):
            _collect_calls(statement.body, statement.name, out)
        elif isinstance(statement, QiecHandlerDecl):
            for clause in statement.clauses:
                _collect_calls(getattr(clause, "body", None), statement.name, out)
        elif isinstance(statement, ProgramDecl):
            _collect_calls(statement.draws, statement.name, out)
    return tuple(out)

call_site_at

call_site_at(module: Module, line: int, col: int) -> CallSite | None

The call whose callee name covers a source position.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

line

The 0-based line.

TYPE: int

col

The 0-based column.

TYPE: int

RETURNS DESCRIPTION
CallSite | None

The call, or None when no callee name covers the position.

Source code in src/quivers/dsl/qiec_tooling.py
def call_site_at(module: Module, line: int, col: int) -> CallSite | None:
    """The call whose callee name covers a source position.

    Parameters
    ----------
    module : Module
        The parsed module.
    line : int
        The 0-based line.
    col : int
        The 0-based column.

    Returns
    -------
    CallSite | None
        The call, or ``None`` when no callee name covers the position.
    """
    for site in call_sites(module):
        if site.line - 1 == line and site.col <= col <= site.col + len(site.callee):
            return site
    return None

instantiated_call_signature

instantiated_call_signature(module: Module, site: CallSite) -> str | None

Render the signature a call instantiates its callee at.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

site

The call.

TYPE: CallSite

RETURNS DESCRIPTION
str | None

The callee's declaration line with each static binder replaced by the call's argument for it, and the define keyword and the trailing = removed: identity[Int](value : Int) : Int !{} for identity[Int](7). None when the callee is not a computation of the module (a program called by name renders through its own signature).

Source code in src/quivers/dsl/qiec_tooling.py
def instantiated_call_signature(module: Module, site: CallSite) -> str | None:
    """Render the signature a call instantiates its callee at.

    Parameters
    ----------
    module : Module
        The parsed module.
    site : CallSite
        The call.

    Returns
    -------
    str | None
        The callee's declaration line with each static binder replaced
        by the call's argument for it, and the ``define`` keyword and
        the trailing ``=`` removed: ``identity[Int](value : Int) : Int !{}``
        for ``identity[Int](7)``. ``None`` when the callee is not a
        computation of the module (a program called by name renders
        through its own signature).
    """
    declaration = next(
        (
            statement
            for statement in module.statements
            if isinstance(statement, QiecComputationDecl)
            and statement.name == site.callee
        ),
        None,
    )
    if declaration is None:
        return None
    rendered = module_to_source(Module(statements=(declaration,)))
    header = rendered.splitlines()[0].strip()
    header = header.removeprefix("define ").removesuffix(" =").rstrip()
    binders = tuple(binder.name for binder in declaration.binders)
    for binder, argument in zip(binders, site.static_arguments):
        header = re.sub(rf"\b{re.escape(binder)}\b", argument, header)
    if declaration.binders:
        # The telescope is instantiated, so the binder list gives way to
        # the arguments the call supplies.
        telescope = "[" + ", ".join(site.static_arguments) + "]"
        header = re.sub(
            r"^(\w+)\[[^\]]*\]", lambda m: f"{m.group(1)}{telescope}", header
        )
    return header

qualified_operation_signature

qualified_operation_signature(module: Module, spelling: str) -> str | None

Render an operation addressed through its interface or an instance.

PARAMETER DESCRIPTION
module

The parsed module.

TYPE: Module

spelling

Effect.op or instance.op, where the interface is the module's own or the prelude's.

TYPE: str

RETURNS DESCRIPTION
str | None

The operation's declaration line under the given spelling, with an instance's static arguments substituted for the interface's binders (cell.get : Unit -> Int for instance cell : State[Int]), or None when nothing of that spelling exists.

Source code in src/quivers/dsl/qiec_tooling.py
def qualified_operation_signature(module: Module, spelling: str) -> str | None:
    """Render an operation addressed through its interface or an instance.

    Parameters
    ----------
    module : Module
        The parsed module.
    spelling : str
        ``Effect.op`` or ``instance.op``, where the interface is the
        module's own or the prelude's.

    Returns
    -------
    str | None
        The operation's declaration line under the given spelling, with
        an instance's static arguments substituted for the interface's
        binders (``cell.get : Unit -> Int`` for ``instance cell :
        State[Int]``), or ``None`` when nothing of that spelling exists.
    """
    owner, _, member = spelling.partition(".")
    effect_name = owner
    arguments: tuple[str, ...] = ()
    instance = next(
        (
            statement
            for statement in module.statements
            if isinstance(statement, QiecEffectInstanceDecl) and statement.name == owner
        ),
        None,
    )
    if instance is not None:
        effect_name = instance.effect.name
        arguments = tuple(
            static_argument_to_source(argument)
            for argument in instance.effect.arguments
        )
    declared = next(
        (
            statement
            for statement in module.statements
            if isinstance(statement, QiecEffectDecl) and statement.name == effect_name
        ),
        None,
    )
    if declared is not None:
        operation = next(
            (item for item in declared.operations if item.name == member), None
        )
        if operation is None:
            return None
        rendered = module_to_source(Module(statements=(declared,)))
        line = next(
            (
                text.strip()
                for text in rendered.splitlines()[1:]
                if re.match(rf"\s*{re.escape(member)}\b", text)
            ),
            None,
        )
        if line is None:
            return None
        binders = tuple(binder.name for binder in declared.binders)
    else:
        prelude = PRELUDE_EFFECTS.get(effect_name)
        if prelude is None:
            return None
        operation = next(
            (item for item in prelude.operations if item.name == member), None
        )
        if operation is None:
            return None
        line = render_operation(operation)
        binders = tuple(binder.name for binder in prelude.telescope)
    for binder, argument in zip(binders, arguments):
        line = re.sub(rf"\b{re.escape(binder)}\b", argument, line)
    return f"{spelling}{line[len(member) :]}"

qiec_module_name

qiec_module_name(file_path: str | Path | None) -> str

Derive a deterministic source module name for editor/CLI entry points.

The name enters every stable identity the lowerer derives, so it has to depend on the path alone and not on how the path was spelled.

PARAMETER DESCRIPTION
file_path

The source path, possibly a file:// URI, or None for input that has no file, such as a REPL line.

TYPE: str or Path or None

RETURNS DESCRIPTION
str

The path's stem, "repl" when there is no path, and "module" when the path has an empty stem.

Source code in src/quivers/dsl/qiec_tooling.py
def qiec_module_name(file_path: str | Path | None) -> str:
    """Derive a deterministic source module name for editor/CLI entry points.

    The name enters every stable identity the lowerer derives, so it has
    to depend on the path alone and not on how the path was spelled.

    Parameters
    ----------
    file_path : str or pathlib.Path or None
        The source path, possibly a ``file://`` URI, or None for input
        that has no file, such as a REPL line.

    Returns
    -------
    str
        The path's stem, ``"repl"`` when there is no path, and
        ``"module"`` when the path has an empty stem.
    """

    if file_path is None:
        return "repl"
    raw = str(file_path)
    if raw.startswith("file://"):
        raw = raw.removeprefix("file://")
    stem = Path(raw).stem
    return stem or "module"