diff --git a/packages/compiler/src/passes/cap-check.ts b/packages/compiler/src/passes/cap-check.ts index e1b3cbd..a6ab354 100644 --- a/packages/compiler/src/passes/cap-check.ts +++ b/packages/compiler/src/passes/cap-check.ts @@ -135,7 +135,7 @@ function checkDirect(src: string, allowGenerics: boolean): string { function checkDirectFn(src: string, tokens: Token[], fn: FnDecl, inner: FnDecl[]): void { const declared = new Set(fn.capabilities); - for (let i = fn.tokenStart; i < fn.tokenEnd; i++) { + for (let i = fn.bodyTokenStart ?? fn.tokenStart; i < fn.tokenEnd; i++) { if (insideAny(i, inner)) continue; const tok = tokens[i]; if (!tok || tok.kind !== "ident") continue; @@ -290,7 +290,7 @@ function scanBody( const callNames = new Set(); const fnNames = new Set(decls.map((d) => d.name)); - for (let i = fn.tokenStart; i < fn.tokenEnd; i++) { + for (let i = fn.bodyTokenStart ?? fn.tokenStart; i < fn.tokenEnd; i++) { if (insideAny(i, inner)) continue; const tok = tokens[i]; if (!tok || tok.kind !== "ident") continue; diff --git a/packages/compiler/src/passes/intent-check.ts b/packages/compiler/src/passes/intent-check.ts index 985d706..71d8c9d 100644 --- a/packages/compiler/src/passes/intent-check.ts +++ b/packages/compiler/src/passes/intent-check.ts @@ -158,7 +158,7 @@ function findFirstCapabilityUse( (g) => g !== fn && g.tokenStart >= fn.tokenStart && g.tokenEnd <= fn.tokenEnd, ); - for (let i = fn.tokenStart; i < fn.tokenEnd; i++) { + for (let i = fn.bodyTokenStart ?? fn.tokenStart; i < fn.tokenEnd; i++) { if (insideAny(i, inner)) continue; const tok = tokens[i]; if (!tok || tok.kind !== "ident") continue; diff --git a/packages/compiler/tests/cap-check.test.ts b/packages/compiler/tests/cap-check.test.ts index 34f9f1e..d246a2e 100644 --- a/packages/compiler/tests/cap-check.test.ts +++ b/packages/compiler/tests/cap-check.test.ts @@ -307,3 +307,28 @@ describe("static capability check (0.2)", () => { } }); }); + +// --------------------------------------------------------------------------- +// Regression: stdlib namespace in parameter type annotation must not fire +// --------------------------------------------------------------------------- + +describe("cap-check: no false positive for stdlib namespace in parameter type", () => { + it("does not fire CAP001 when stdlib namespace appears in parameter type annotation", () => { + // `http.Client` is a type annotation, not a capability call. Scanning from + // fn.tokenStart used to flag this as http.x and fire CAP001. + const src = "?bs 0.9\nfn handleReq(client: http.Client) -> string = \"ok\"\n"; + expect(() => t(src)).not.toThrow(); + }); + + it("does not fire CAP001 when stdlib namespace appears only in return type annotation", () => { + // Return type `http.Client` is a type annotation, not a capability call. + // No stdlib call in the body — should compile clean. + const src = "?bs 0.9\nfn makeClient() -> http.Client = \"placeholder\"\n"; + expect(() => t(src)).not.toThrow(); + }); + + it("still fires CAP001 when stdlib call is in the body (not the header)", () => { + const src = "?bs 0.9\nfn fetchData(url: string) -> string {\n http.get(url)\n}\n"; + expect(() => t(src)).toThrow(/CAP001/); + }); +});