Skip to content

fix(dep-check): restrict collectCallees scan to body tokens only#71

Merged
marcelofarias merged 2 commits into
mainfrom
botkowski/fix-callee-scan-scope
May 20, 2026
Merged

fix(dep-check): restrict collectCallees scan to body tokens only#71
marcelofarias merged 2 commits into
mainfrom
botkowski/fix-callee-scan-scope

Conversation

@marcelofarias
Copy link
Copy Markdown
Owner

Summary

Fixes #70.

collectCallees in dep-check.ts was scanning from fn.tokenStart (the unsafe/fn keyword token) through fn.tokenEnd. This means idents in the parameter list and return-type annotation could be mistaken for body calls.

Concrete case from #70:

?bs 0.9
fn helper() reads { cache } -> string = "x"
fn caller(x: string = helper()) -> string = x

helper appears in caller's parameter default — evaluated at the call site, not in the body. DEP001 was firing on caller for missing reads { cache } even though the body never calls helper.

Fix

  • Add bodyTokenStart: number to FnDecl — the token-array index of the { or = that opens the body (i.e. typeEnd from parseFn).
  • Change collectCallees to start its scan at fn.bodyTokenStart instead of fn.tokenStart, skipping the parameter list and return-type annotation.

Note on open PRs

The botkowski/throws-header (PR #64) and botkowski/thr002 (PR #66) branches include _callgraph.ts which extracts collectCallees into a shared module. Those PRs will need to pick up bodyTokenStart from FnDecl and apply the same fix to collectCallees in _callgraph.ts. The botkowski/uns005-v2 branch also has a similar scan in uns-check.ts that will need the same treatment.

Test plan

  • pnpm -r build && pnpm test — 549/549 pass (2 new regression tests)
  • parameter default caller(x = helper()) — no DEP001
  • body call caller() { helper() } — DEP001 still fires

🤖 Generated with Claude Code

collectCallees was scanning from fn.tokenStart (the `unsafe`/`fn` keyword)
through tokenEnd, so idents in the parameter list and return-type annotation
could be mistaken for body calls. A parameter default like:

  fn caller(x: string = helper()) -> string = x

would cause DEP001 to fire on `caller` for missing `reads { cache }` even
though `helper()` is evaluated at the call site, not in the body.

Fix: add `bodyTokenStart` to FnDecl (the token index of `{` or `=` that
opens the body) and start the collectCallees scan there instead of
fn.tokenStart.

Closes #70.

Co-Authored-By: Botkowski <noreply@anthropic.com>
@marcelofarias marcelofarias requested a review from Copilot May 20, 2026 00:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes DEP001 false-positives caused by collectCallees scanning the entire fn token range (including parameter lists and return-type annotations) instead of restricting analysis to the function body.

Changes:

  • Add bodyTokenStart to FnDecl to record the token index where the function body begins ({ or =).
  • Update collectCallees in dep-check.ts to start scanning at fn.bodyTokenStart rather than fn.tokenStart.
  • Add regression tests covering the parameter-default false-positive case and a body-call sanity check.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/compiler/tests/dep-check.test.ts Adds regression coverage for DEP001 behavior around parameter defaults (and asserts body calls still trigger).
packages/compiler/src/passes/dep-check.ts Restricts callee token scanning to the body region via bodyTokenStart.
packages/compiler/src/parser/parse-fn.ts Extends FnDecl with bodyTokenStart and populates it during parsing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +195 to +206
// ---------------------------------------------------------------------------
// Parameter-default / return-type false-positive regression (issue #70)
// ---------------------------------------------------------------------------

describe("parameter default and return-type exclusion", () => {
it("does not fire DEP001 when callee appears only in a parameter default, not the body", () => {
// `helper` is called in the parameter default of `caller` (evaluated at the
// call site), not in caller's body. collectCallees must not pick it up.
const src =
"?bs 0.9\n" +
"fn helper() reads { cache } -> string = \"x\"\n" +
"fn caller(x: string = helper()) -> string = x\n";
Comment on lines +109 to +111
* parameter list or return-type annotation.
*/
bodyTokenStart: number;
- Make bodyTokenStart optional in FnDecl to avoid breaking downstream
  code that constructs FnDecl values structurally (mocks, tooling); add
  ?? fn.tokenStart fallback in collectCallees
- Rename test describe block to accurately reflect parameter-default-only
  coverage; add comment explaining return-type exclusion is implicit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@marcelofarias marcelofarias merged commit 8904a6b into main May 20, 2026
6 checks passed
@marcelofarias marcelofarias deleted the botkowski/fix-callee-scan-scope branch May 20, 2026 08:06
marcelofarias added a commit that referenced this pull request May 20, 2026
…01/INT002 on param types (#75)

* fix(cap-check,intent-check): scan from bodyTokenStart to avoid false positives on parameter/return types

CAP001/CAP002 (cap-check.ts) and INT002 (intent-check.ts) were scanning
from fn.tokenStart, which includes the fn keyword, name, parameter list,
and return type annotation. This caused false positives when a stdlib
namespace identifier (http, fs, time, …) appeared in a type annotation
rather than an actual capability call — for example:

  fn handleReq(client: http.Client) -> string = "ok"
  // ^^ CAP001 fired on http.Client even though no capability is consumed

The fix mirrors the approach from PR #71 (dep-check) and the thr002 branch
(thr-check): start the scan at fn.bodyTokenStart ?? fn.tokenStart so the
parameter list and return type annotation are skipped entirely.

Adds regression tests to cap-check.test.ts confirming stdlib namespace in
parameter type annotations no longer triggers CAP001.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(cap-check): strengthen return-type annotation regression test

Replace the assertion-free second test case with one that actually
verifies no CAP001 fires when the stdlib namespace appears only in
the return type (body is a plain literal, not a stdlib call).

Also removes the misleading "double-fire" comment: cap-check
short-circuits on first error so double-fire is impossible; the
real concern is false positives from type annotations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Marcelo Farias <mfarias@Marcelos-MacBook-Pro-4.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: collectCallees scans from fn.tokenStart, matching idents in parameter types and defaults

2 participants