/** * Reading one function body: what it raises directly, and what it calls. * * Split from the propagation pass so the "what does this body do" question has * one home. The type checker and the cross-file effect pass both ask it, and * two separate answers is how effects came to mean different things on either * side of an import (GitHub issue 680). * * Leaf module: it may import the walker and type declarations, nothing else. */ import { type WalkAncestor } from "../utils/node.js"; import type { AgencyNode, Expression } from "../types.js"; import type { FunctionCall } from "../types/function.js"; import type { SplatExpression, NamedArgument } from "../types/dataStructures.js"; export type BodyFacts = { /** Effect labels raised by a literal `interrupt` in this body. */ effects: string[]; /** Local names of everything this body calls, unresolved. */ callees: string[]; /** Every call node seen. Handed back so the type checker can read call * arguments without walking the body a second time. */ calls: FunctionCall[]; }; export declare function collectBodyFacts(body: AgencyNode[]): BodyFacts; /** * The same reading, keeping only the raises and calls that no handler in this * body answers. `read(f) with approve` is left out, a bare `read(f)` is kept. * * This is what decides whether a function is safe to call where no handler and * no checkpoint can exist, such as a static initializer (issue #912). */ export declare function collectUnansweredFacts(body: AgencyNode[]): BodyFacts; /** * A name written inside a call's arguments, such as `read` in `helper(read)` * and in `llm(p, tools: [read])`, or null. The callee may call what it is * handed, so a caller that asks what can be raised counts it as called. A name * that is not a function finds no effects and adds nothing. */ export declare function passedName(node: AgencyNode, ancestors: WalkAncestor[]): string | null; /** * Whether a `handle` block or a `with approve` / `with reject` answers an * interrupt raised at `node`. `with propagate` answers nothing, so it does not * count. * * A `handle` block answers its protected body only. A raise written in the * handler's own body is not answered by that handler: * * handle { read(f) } with (intr) { write(g) } * * `read(f)` is answered and `write(g)` is not. * * What the handler decides is not read. A handler that returns `propagate()` * still counts as answering its protected body. */ export declare function isInsideHandler(ancestors: WalkAncestor[], node: AgencyNode): boolean; /** * The name a call site names, or null when it names nothing this analysis can * use. * * A plain `g(...)` names `g`. A method call inside an access chain names * nothing global: `xs.map(...)` and `f.partial(method: "GET")` call methods on * a value, so recording `map` or `partial` would collide with any function of * that name and attribute its effects to an unrelated call. The type checker * excludes these for the same reason (functionTypeRaises.ts:106). * * Working out what a method call reaches needs the receiver's type, which this * walk does not have. That makes it one of the blind spots the splice * eligibility check refuses on. */ export declare function calledName(node: FunctionCall, ancestors: WalkAncestor[]): string | null; /** * The expression inside a call argument, whatever shape the argument takes. * * `f(x)`, `f(name: x)` and `f(...xs)` all carry an expression, and anything * inspecting arguments has to unwrap all three or it silently skips the two * that are most likely to carry a callback. */ export declare function argumentExpression(arg: Expression | SplatExpression | NamedArgument): Expression; /** Deduplicate, preserving first-seen order. The declarative counterpart to * addUnique, for code that builds a list rather than growing one. */ export declare function unique(values: string[]): string[]; /** Grow a list in place. For code that accumulates rather than builds. */ export declare function addUnique(arr: string[], value: string): void;