From bf883308b4ce9d5a8029a790342bcc142e83caff Mon Sep 17 00:00:00 2001 From: Jason Bowman Date: Mon, 27 Jul 2026 13:41:14 -0700 Subject: [PATCH] fix(extract): descend past a Nix file's root function so its bindings are reached MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Nix library or module file's root expression is normally itself a function — `{ pkgs, lib, ... }: ` — the near-universal header for the ecosystem. That node matches nix_func_types, so walk_defs handed it to extract_func_def, which resolved no name for it (the Nix resolver requires the function's parent to be a `binding`; the root lambda's parent is the file root, `source_code`) and minted nothing. walk_defs then hit `if (!descend_into_func) continue;` and abandoned the whole subtree, so no binding below the header was ever visited. The effect was total for the dominant file shape, and invisible for the rest: a file opening with a bare `let` or attrset was walked normally, so the language looked supported. Measured on a 320-file Nix repository, 253 of whose files carry a function header: before after Function 48 1900 CALLS 56 1443 files w/defs 8 258 Adding CBM_LANG_NIX to descend_into_func is sufficient. Descending does not over-mint from curried lambdas: in `f = a: b: ...` the inner `b:` has a function_expression parent, resolves no name, and stays out. Checked clean for over-minting on map/foldl' arguments, `with` bodies, `inherit`, and parenthesized lambdas; rec attrsets, nested attrsets, `let` inside a function body, and factory-returned attrsets all mint correctly. Call attribution is unaffected. CALLS scope is computed by a separate traversal (push_boundary_scopes -> compute_func_qn) that calls the same shared cbm_resolve_func_name this path uses, so definition names and call-scope names agree by construction, and a scope is pushed only when that name resolves non-NULL. The edge-count rise is previously-dropped edges being written, not re-attributed ones: before the fix those edges named a source node that did not exist. The change is gated on ctx->language, so no other language's walk is affected. Signed-off-by: Jason Bowman --- internal/cbm/extract_defs.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/internal/cbm/extract_defs.c b/internal/cbm/extract_defs.c index 7cc4177ef..073f11e20 100644 --- a/internal/cbm/extract_defs.c +++ b/internal/cbm/extract_defs.c @@ -6758,10 +6758,16 @@ static void walk_defs(CBMExtractCtx *ctx, TSNode root, const CBMLangSpec *spec, // Ada subprograms nest (a procedure body's declarative part can // contain inner subprogram bodies); descend so the nested defs // are captured and same-file calls to them resolve to a CALLS edge. + // Nix: a library/module file's ROOT expression is normally itself a + // function_expression (`{ pkgs, lib, ... }: `), so stopping here + // abandons the entire file — every binding in it is lost. Descend so + // the body's `name = args: ...` bindings are reached. Inner curried + // lambdas (`f = a: b: ...`) resolve no name and mint nothing, so the + // extra descent adds defs without adding noise. bool descend_into_func = (ctx->language == CBM_LANG_WOLFRAM || ctx->language == CBM_LANG_TYPESCRIPT || ctx->language == CBM_LANG_JAVASCRIPT || ctx->language == CBM_LANG_TSX || - ctx->language == CBM_LANG_ADA); + ctx->language == CBM_LANG_ADA || ctx->language == CBM_LANG_NIX); if (!descend_into_func) { continue; }