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
4 changes: 2 additions & 2 deletions packages/compiler/src/passes/cap-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Comment on lines 136 to 140
if (!tok || tok.kind !== "ident") continue;
Expand Down Expand Up @@ -290,7 +290,7 @@ function scanBody(
const callNames = new Set<string>();
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;
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler/src/passes/intent-check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 25 additions & 0 deletions packages/compiler/tests/cap-check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/);
});
});
Loading