Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/compiler/src/parser/parse-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,17 @@ export interface FnDecl {
/** Source offset of the unsafe reason string token (UTF-16 code units, inclusive). Used to anchor UNS002 at the right location. */
unsafeReasonStart?: number;
returnType: string;
/**
* Token-array index of the first body token (`{` for block bodies, `=` for
* expression bodies). Effect-check passes use this to scan only from the body
* start rather than from `tokenStart`, avoiding false matches on idents in the
* parameter list or return-type annotation.
*
* Optional so that code constructing `FnDecl` values outside the parser (e.g.
* tests, mocks) is not forced to populate this field. Consumers should fall
* back to `tokenStart` when absent: `fn.bodyTokenStart ?? fn.tokenStart`.
*/
bodyTokenStart?: number;
/** Body is a brace block OR a single-expression form (= pure / io / arbitrary). */
body: FnBody;
}
Expand Down Expand Up @@ -489,6 +500,7 @@ export function parseFn(
unsafeReason,
unsafeReasonStart,
returnType,
bodyTokenStart: typeEnd,
body,
};
}
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/passes/dep-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ function collectCallees(
const open: FnDecl[] = [];
let nextInner = 0;

for (let i = fn.tokenStart; i < fn.tokenEnd; i++) {
for (let i = fn.bodyTokenStart ?? fn.tokenStart; i < fn.tokenEnd; i++) {
while (open.length > 0 && open[open.length - 1]!.tokenEnd <= i) open.pop();
while (nextInner < inner.length && inner[nextInner]!.tokenStart <= i) {
open.push(inner[nextInner]!);
Expand Down
28 changes: 28 additions & 0 deletions packages/compiler/tests/dep-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,31 @@ describe("recursive fns", () => {
expect(() => compile(src)).not.toThrow();
});
});

// ---------------------------------------------------------------------------
// Parameter-default false-positive regression (issue #70)
// collectCallees now starts from bodyTokenStart, skipping both the parameter
// list (including defaults) and the return-type annotation. The return-type
// exclusion is implicitly covered by the same mechanism — botscript return
// types don't support call-syntax idents, so no separate test is needed.
// ---------------------------------------------------------------------------

describe("parameter-default exclusion (issue #70)", () => {
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 +195 to +210
expect(() => compile(src)).not.toThrow();
});

it("still fires DEP001 when callee is called inside the body", () => {
const src =
"?bs 0.9\n" +
"fn helper() reads { cache } -> string = \"x\"\n" +
"fn caller() -> string { helper() }\n";
expect(() => compile(src)).toThrow("DEP001");
});
});
Loading